Trying to simplify.
This commit is contained in:
parent
7f9400b395
commit
edca3f0d1b
|
@ -9,7 +9,7 @@ describe('The LayerStack component', () => {
|
||||||
render(
|
render(
|
||||||
<LayerStack
|
<LayerStack
|
||||||
numberOfTiledLayers={5}
|
numberOfTiledLayers={5}
|
||||||
keyObject={{ provider: 'xxx', zoomLevel: 9, x: 777, y: 333 }}
|
keyObject={{ provider: 'fake', zoomLevel: 9, x: 777, y: 333 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const svg = screen.getByTestId('layer-stack');
|
const svg = screen.getByTestId('layer-stack');
|
||||||
|
@ -273,7 +273,7 @@ describe('The LayerStack component', () => {
|
||||||
render(
|
render(
|
||||||
<LayerStack
|
<LayerStack
|
||||||
numberOfTiledLayers={3}
|
numberOfTiledLayers={3}
|
||||||
keyObject={{ provider: 'xxx', zoomLevel: 9, x: 777, y: 333 }}
|
keyObject={{ provider: 'fake', zoomLevel: 9, x: 777, y: 333 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const svg = screen.getByTestId('layer-stack');
|
const svg = screen.getByTestId('layer-stack');
|
||||||
|
@ -529,7 +529,7 @@ describe('The LayerStack component', () => {
|
||||||
render(
|
render(
|
||||||
<LayerStack
|
<LayerStack
|
||||||
numberOfTiledLayers={3}
|
numberOfTiledLayers={3}
|
||||||
keyObject={{ provider: 'xxx', zoomLevel: 9, x: 777, y: 333 }}
|
keyObject={{ provider: 'fake', zoomLevel: 9, x: 777, y: 333 }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const { result } = renderHook(() => [
|
const { result } = renderHook(() => [
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import react, { useCallback, useEffect, useRef, useState } from 'react';
|
import react from 'react';
|
||||||
import { atom, useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
import { TileFactory, TileKeyObject } from './types';
|
import { TileKeyObject } from './types';
|
||||||
|
|
||||||
import { coordinateSystemAtom } from './Map';
|
import { coordinateSystemAtom } from './Map';
|
||||||
import TiledLayer from './TiledLayer';
|
import TileSet from './TileSet';
|
||||||
import Tile from './Tile';
|
|
||||||
import _, { floor, range } from 'lodash';
|
import _, { floor, range } from 'lodash';
|
||||||
import tileUri from './uris';
|
import tileUri from './uris';
|
||||||
|
|
||||||
|
@ -49,24 +48,34 @@ export const LayerStack: react.FC<LayerStackProperties> = (
|
||||||
|
|
||||||
const viewPort = {
|
const viewPort = {
|
||||||
topLeft: {
|
topLeft: {
|
||||||
x: Math.floor(-coordinateSystem.shift.x / coordinateSystem.zoom / 256),
|
x:
|
||||||
y: Math.floor(-coordinateSystem.shift.y / coordinateSystem.zoom / 256),
|
props.keyObject.x +
|
||||||
|
Math.floor(-coordinateSystem.shift.x / coordinateSystem.zoom / 256),
|
||||||
|
y:
|
||||||
|
props.keyObject.y +
|
||||||
|
Math.floor(-coordinateSystem.shift.y / coordinateSystem.zoom / 256),
|
||||||
},
|
},
|
||||||
bottomRight: {
|
bottomRight: {
|
||||||
x: Math.ceil(
|
x:
|
||||||
(-coordinateSystem.shift.x + window.innerWidth) /
|
props.keyObject.x +
|
||||||
coordinateSystem.zoom /
|
Math.ceil(
|
||||||
256
|
(-coordinateSystem.shift.x + window.innerWidth) /
|
||||||
),
|
coordinateSystem.zoom /
|
||||||
y: Math.ceil(
|
256
|
||||||
(-coordinateSystem.shift.y + window.innerHeight) /
|
) -
|
||||||
coordinateSystem.zoom /
|
1,
|
||||||
256
|
y:
|
||||||
),
|
props.keyObject.y +
|
||||||
|
Math.ceil(
|
||||||
|
(-coordinateSystem.shift.y + window.innerHeight) /
|
||||||
|
coordinateSystem.zoom /
|
||||||
|
256
|
||||||
|
) -
|
||||||
|
1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTiledLayer = (i: number) => {
|
const getTileSet = (i: number) => {
|
||||||
const relativeZoomLevel = i - Math.floor(numberOfTiledLayers / 2);
|
const relativeZoomLevel = i - Math.floor(numberOfTiledLayers / 2);
|
||||||
const zoom = 2 ** relativeZoomLevel;
|
const zoom = 2 ** relativeZoomLevel;
|
||||||
const origin = {
|
const origin = {
|
||||||
|
@ -90,11 +99,13 @@ export const LayerStack: react.FC<LayerStackProperties> = (
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g
|
<g
|
||||||
transform={`scale(${256 / zoom}) translate(${shift.x}, ${shift.y})`}
|
transform={`scale(${256 / zoom}) translate(${shift.x - keyObject.x}, ${
|
||||||
|
shift.y - keyObject.y
|
||||||
|
})`}
|
||||||
key={key}
|
key={key}
|
||||||
data-testid={key}
|
data-testid={key}
|
||||||
>
|
>
|
||||||
<TiledLayer
|
<TileSet
|
||||||
key={key}
|
key={key}
|
||||||
keyObject={keyObject}
|
keyObject={keyObject}
|
||||||
viewPort={i === activeTiledLayer ? viewPort : undefined}
|
viewPort={i === activeTiledLayer ? viewPort : undefined}
|
||||||
|
@ -116,17 +127,15 @@ export const LayerStack: react.FC<LayerStackProperties> = (
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
// Tiled layers with less detail
|
// Tiled layers with less detail
|
||||||
range(0, activeTiledLayer).map(getTiledLayer)
|
range(0, activeTiledLayer).map(getTileSet)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Tiled layers with more details
|
// Tiled layers with more details
|
||||||
range(numberOfTiledLayers - 1, activeTiledLayer, -1).map(
|
range(numberOfTiledLayers - 1, activeTiledLayer, -1).map(getTileSet)
|
||||||
getTiledLayer
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// And the active one
|
// And the active one
|
||||||
getTiledLayer(activeTiledLayer)
|
getTileSet(activeTiledLayer)
|
||||||
}
|
}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import Tile from './Tile';
|
import Tile from './Tile';
|
||||||
|
import { TileKeyObject } from './types';
|
||||||
|
|
||||||
describe('The Tile component ', () => {
|
describe('The Tile component ', () => {
|
||||||
|
const keyObject: TileKeyObject = {
|
||||||
|
provider: 'fake',
|
||||||
|
zoomLevel: 10,
|
||||||
|
x: 1,
|
||||||
|
y: 2,
|
||||||
|
};
|
||||||
test('Is initially empty', () => {
|
test('Is initially empty', () => {
|
||||||
const { baseElement } = render(
|
const { baseElement } = render(
|
||||||
<svg>
|
<svg>
|
||||||
<Tile href='http://fakeurl' delay={10000} />
|
<Tile keyObject={keyObject} delay={10000} />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
// screen.debug();
|
// screen.debug();
|
||||||
|
@ -13,7 +20,9 @@ describe('The Tile component ', () => {
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<svg>
|
<svg>
|
||||||
<g />
|
<g
|
||||||
|
transform="translate(1, 2)"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -22,7 +31,7 @@ describe('The Tile component ', () => {
|
||||||
test('Gets its image immediately with a fake URL', () => {
|
test('Gets its image immediately with a fake URL', () => {
|
||||||
const { baseElement } = render(
|
const { baseElement } = render(
|
||||||
<svg>
|
<svg>
|
||||||
<Tile href='http://fakeurl' />
|
<Tile keyObject={keyObject} />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
// screen.debug();
|
// screen.debug();
|
||||||
|
@ -30,10 +39,12 @@ describe('The Tile component ', () => {
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<svg>
|
<svg>
|
||||||
<g>
|
<g
|
||||||
|
transform="translate(1, 2)"
|
||||||
|
>
|
||||||
<image
|
<image
|
||||||
height="1"
|
height="1"
|
||||||
href="http://fakeurl"
|
href="https://fakeurl/10/1/2.png"
|
||||||
width="1"
|
width="1"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import react, { memo, useEffect, useRef } from 'react';
|
import react, { memo, useEffect, useRef } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import { TileKeyObject } from './types';
|
||||||
|
import { getTileUrl } from './tile-providers';
|
||||||
|
|
||||||
export interface TileProperties {
|
export interface TileProperties {
|
||||||
/** The image's source URL */
|
/** The image's source URL */
|
||||||
href: string;
|
keyObject: TileKeyObject;
|
||||||
/** A delay to add (for test/debug purposes) */
|
/** A delay to add (for test/debug purposes) */
|
||||||
delay?: number;
|
delay?: number;
|
||||||
}
|
}
|
||||||
|
@ -27,14 +29,15 @@ export const Tile: react.FC<TileProperties> = memo((props: TileProperties) => {
|
||||||
const timeout = (ms: number) => {
|
const timeout = (ms: number) => {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
};
|
};
|
||||||
// console.log(`Rendering tile: ${props.href}`);
|
// console.log(`Rendering tile: ${JSON.stringify(props)}`);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadImage = async () => {
|
const loadImage = async () => {
|
||||||
// console.log(`Pre loading: ${props.href}`);
|
// console.log(`Pre loading: ${props.href}`);
|
||||||
|
const href = getTileUrl(props.keyObject);
|
||||||
const image = new Image(1, 1);
|
const image = new Image(1, 1);
|
||||||
image.loading = 'eager';
|
image.loading = 'eager';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
image.setAttribute('href', props.href);
|
image.setAttribute('href', href);
|
||||||
if (!image.complete) {
|
if (!image.complete) {
|
||||||
await image.decode();
|
await image.decode();
|
||||||
}
|
}
|
||||||
|
@ -48,13 +51,18 @@ export const Tile: react.FC<TileProperties> = memo((props: TileProperties) => {
|
||||||
svgImage.setAttribute('width', '1');
|
svgImage.setAttribute('width', '1');
|
||||||
svgImage.setAttribute('height', '1');
|
svgImage.setAttribute('height', '1');
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
svgImage.setAttribute('href', props.href);
|
svgImage.setAttribute('href', href);
|
||||||
g.current?.replaceChildren(svgImage);
|
g.current?.replaceChildren(svgImage);
|
||||||
};
|
};
|
||||||
loadImage();
|
loadImage();
|
||||||
}, [props.href]);
|
}, [props.keyObject]);
|
||||||
|
|
||||||
return <g ref={g} />;
|
return (
|
||||||
|
<g
|
||||||
|
ref={g}
|
||||||
|
transform={`translate(${props.keyObject.x}, ${props.keyObject.y})`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}, isEqual);
|
}, isEqual);
|
||||||
|
|
||||||
export default Tile;
|
export default Tile;
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import TileSet from './TileSet';
|
||||||
|
|
||||||
|
describe('The TiledLayer component ', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
globalThis.cacheForTileSet = new Map();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exposes the tiles needed per its viewport', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 5, y: 8 }}
|
||||||
|
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
// screen.debug();
|
||||||
|
expect(baseElement).toMatchInlineSnapshot(`
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<g
|
||||||
|
transform="translate(1, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/2/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/3/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds more tiles when its viewport is updated without removing the previous ones', () => {
|
||||||
|
const { baseElement, rerender } = render(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 5, y: 8 }}
|
||||||
|
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
rerender(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 5, y: 8 }}
|
||||||
|
viewPort={{ topLeft: { x: 5, y: 0 }, bottomRight: { x: 5, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
// screen.debug();
|
||||||
|
expect(baseElement).toMatchInlineSnapshot(`
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<g
|
||||||
|
transform="translate(1, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/2/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/3/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 0)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/0.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/1.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
test('is not reinitialized if its key isObject updated', () => {
|
||||||
|
const { baseElement, rerender } = render(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 5, y: 8 }}
|
||||||
|
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
rerender(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 4, y: 10 }}
|
||||||
|
viewPort={{ topLeft: { x: 5, y: 0 }, bottomRight: { x: 5, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
// screen.debug();
|
||||||
|
expect(baseElement).toMatchInlineSnapshot(`
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<g
|
||||||
|
transform="translate(1, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/2/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/3/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 0)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/0.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/1.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/5/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
test('Also works with negative coordinates', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<svg>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 10, x: 5, y: 8 }}
|
||||||
|
viewPort={{ topLeft: { x: -3, y: -1 }, bottomRight: { x: -2, y: 2 } }}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
// screen.debug();
|
||||||
|
expect(baseElement).toMatchInlineSnapshot(`
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<svg>
|
||||||
|
<g
|
||||||
|
transform="translate(-3, -1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1021/1023.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-2, -1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1022/1023.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-3, 0)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1021/0.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-2, 0)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1022/0.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-3, 1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1021/1.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-2, 1)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1022/1.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-3, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1021/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-2, 2)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/10/1022/2.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,9 +1,9 @@
|
||||||
import react, { memo, useRef } from 'react';
|
import react, { } from 'react';
|
||||||
import { range, isEqual } from 'lodash';
|
import { range } from 'lodash';
|
||||||
|
|
||||||
import { Rectangle, TileFactory, TileKeyObject } from './types';
|
import { Rectangle, TileKeyObject } from './types';
|
||||||
import tileUri from './uris';
|
import tileUri from './uris';
|
||||||
import tileFactory from './tile-factory';
|
import Tile from './Tile';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -15,22 +15,22 @@ export const thisIsAModule = true;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var cacheForTiledLayer: any;
|
var cacheForTileSet: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
//export {};
|
//export {};
|
||||||
|
|
||||||
globalThis.cacheForTiledLayer = new Map();
|
globalThis.cacheForTileSet = new Map();
|
||||||
|
|
||||||
export interface TiledLayerProperties {
|
export interface TileSetProperties {
|
||||||
/** The key of the first (ie top/left) tile */
|
/** A partial Tile key object specifying the provider and zoom level */
|
||||||
keyObject: TileKeyObject;
|
keyObject: TileKeyObject;
|
||||||
/** The current viewport expressed in tiles coordinates */
|
/** The current viewport expressed in tiles coordinates */
|
||||||
viewPort?: Rectangle;
|
viewPort?: Rectangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lazily loaded layer of tiles.
|
* A lazily loaded set of tiles.
|
||||||
*
|
*
|
||||||
* This component is rather dumb and is mainly a sparse array of tiles.
|
* This component is rather dumb and is mainly a sparse array of tiles.
|
||||||
*
|
*
|
||||||
|
@ -49,13 +49,10 @@ export interface TiledLayerProperties {
|
||||||
*
|
*
|
||||||
* TODO: cache housekeeping
|
* TODO: cache housekeeping
|
||||||
*
|
*
|
||||||
* TODO: test tiles'X and Y boundaries.
|
|
||||||
*
|
|
||||||
* TODO: remove tileFactory to facilitate memoisation.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const TiledLayer: react.FC<TiledLayerProperties> = (
|
export const TileSet: react.FC<TileSetProperties> = (
|
||||||
props: TiledLayerProperties
|
props: TileSetProperties
|
||||||
) => {
|
) => {
|
||||||
// console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`);
|
// console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`);
|
||||||
|
|
||||||
|
@ -64,7 +61,7 @@ export const TiledLayer: react.FC<TiledLayerProperties> = (
|
||||||
zoomLevel: props.keyObject.zoomLevel,
|
zoomLevel: props.keyObject.zoomLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tiles: any = globalThis.cacheForTiledLayer.get(key) ?? new Map();
|
const tiles: any = globalThis.cacheForTileSet.get(key) ?? new Map();
|
||||||
if (props.viewPort !== undefined) {
|
if (props.viewPort !== undefined) {
|
||||||
range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).forEach(
|
range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).forEach(
|
||||||
(row) => {
|
(row) => {
|
||||||
|
@ -82,18 +79,24 @@ export const TiledLayer: react.FC<TiledLayerProperties> = (
|
||||||
if (!tiles.has(key)) {
|
if (!tiles.has(key)) {
|
||||||
tiles.set(
|
tiles.set(
|
||||||
key,
|
key,
|
||||||
<g key={key} transform={`translate(${col}, ${row})`}>
|
<Tile
|
||||||
{tileFactory(keyObject)}
|
key={key}
|
||||||
</g>
|
keyObject={{
|
||||||
|
provider: props.keyObject.provider,
|
||||||
|
zoomLevel: props.keyObject.zoomLevel,
|
||||||
|
x: col,
|
||||||
|
y: row,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
globalThis.cacheForTiledLayer.set(key, tiles);
|
globalThis.cacheForTileSet.set(key, tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{Array.from(tiles.values())}</>;
|
return <>{Array.from(tiles.values())}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TiledLayer;
|
export default TileSet;
|
|
@ -1,356 +0,0 @@
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import TiledLayer from './TiledLayer';
|
|
||||||
import { TileFactory } from './types';
|
|
||||||
|
|
||||||
describe('The TiledLayer component ', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
globalThis.cacheForTiledLayer = new Map();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('exposes the tiles needed per its viewport', () => {
|
|
||||||
const { baseElement } = render(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 5, y: 8 }}
|
|
||||||
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
// screen.debug();
|
|
||||||
expect(baseElement).toMatchInlineSnapshot(`
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<svg>
|
|
||||||
<g
|
|
||||||
transform="translate(1, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/6/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(2, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/7/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(3, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/8/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('adds more tiles when its viewport is updated without removing the previous ones', () => {
|
|
||||||
const { baseElement, rerender } = render(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 5, y: 8 }}
|
|
||||||
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
rerender(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 5, y: 8 }}
|
|
||||||
viewPort={{ topLeft: { x: 5, y: 0 }, bottomRight: { x: 5, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
// screen.debug();
|
|
||||||
expect(baseElement).toMatchInlineSnapshot(`
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<svg>
|
|
||||||
<g
|
|
||||||
transform="translate(1, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/6/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(2, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/7/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(3, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/8/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 0)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/10/8.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/10/9.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/10/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
test('is not reinitialized if its key isObject updated', () => {
|
|
||||||
const { baseElement, rerender } = render(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 5, y: 8 }}
|
|
||||||
viewPort={{ topLeft: { x: 1, y: 2 }, bottomRight: { x: 3, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
rerender(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 4, y: 10 }}
|
|
||||||
viewPort={{ topLeft: { x: 5, y: 0 }, bottomRight: { x: 5, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
// screen.debug();
|
|
||||||
expect(baseElement).toMatchInlineSnapshot(`
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<svg>
|
|
||||||
<g
|
|
||||||
transform="translate(1, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/6/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(2, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/7/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(3, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/8/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 0)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/9/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/9/11.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(5, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/9/12.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
test('Also works with negative coordinates', () => {
|
|
||||||
const { baseElement } = render(
|
|
||||||
<svg>
|
|
||||||
<TiledLayer
|
|
||||||
keyObject={{ provider: 'osm', zoomLevel: 10, x: 5, y: 8 }}
|
|
||||||
viewPort={{ topLeft: { x: -3, y: -1 }, bottomRight: { x: -2, y: 2 } }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
// screen.debug();
|
|
||||||
expect(baseElement).toMatchInlineSnapshot(`
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<svg>
|
|
||||||
<g
|
|
||||||
transform="translate(-3, -1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/2/7.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-2, -1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/3/7.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-3, 0)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/2/8.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-2, 0)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/3/8.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-3, 1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/2/9.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-2, 1)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/3/9.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-3, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/2/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="translate(-2, 2)"
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<image
|
|
||||||
height="1"
|
|
||||||
href="https://tile.openstreetmap.org/10/3/10.png"
|
|
||||||
width="1"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,15 +0,0 @@
|
||||||
import Tile from './Tile';
|
|
||||||
import { TileFactory, TileKeyObject } from './types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param keyObject The tile identifier
|
|
||||||
* @returns The `<Tile>` component
|
|
||||||
*/
|
|
||||||
export const tileFactory: TileFactory = (keyObject:TileKeyObject) => (
|
|
||||||
<Tile
|
|
||||||
href={`https://tile.openstreetmap.org/${keyObject.zoomLevel}/${keyObject.x}/${keyObject.y}.png`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default tileFactory;
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
import Tile from './Tile';
|
||||||
|
import { TileFactory, TileKeyObject } from './types';
|
||||||
|
|
||||||
|
export interface TileProvider {
|
||||||
|
name: string;
|
||||||
|
minZoom: number;
|
||||||
|
maxZoom: number;
|
||||||
|
getTileUrl: { (zoom: number, x: number, y: number): string };
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRandomItem = (items: any[]) => {
|
||||||
|
const idx = Math.floor(Math.random() * items.length);
|
||||||
|
return items[idx];
|
||||||
|
};
|
||||||
|
|
||||||
|
const abc = ['a', 'b', 'c'];
|
||||||
|
|
||||||
|
const tileProviders: any = {
|
||||||
|
osm: {
|
||||||
|
name: 'Open Street Map',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png',
|
||||||
|
},
|
||||||
|
osmfr: {
|
||||||
|
name: 'Open Street Map France',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 20,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile.openstreetmap.fr/osmfr/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
otm: {
|
||||||
|
name: 'Open Topo Map',
|
||||||
|
minZoom: 2,
|
||||||
|
maxZoom: 17,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile.opentopomap.org/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
cyclosm: {
|
||||||
|
name: 'CyclOSM',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 19,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile-cyclosm.openstreetmap.fr/cyclosm/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
//https://b.tile.openstreetmap.fr/openriverboatmap/20/535762/382966.png
|
||||||
|
openriverboatmap: {
|
||||||
|
name: 'Open River Boat Map',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 20,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://' +
|
||||||
|
getRandomItem(abc) +
|
||||||
|
'.tile.openstreetmap.fr/openriverboatmap/' +
|
||||||
|
zoom +
|
||||||
|
'/' +
|
||||||
|
x +
|
||||||
|
'/' +
|
||||||
|
y +
|
||||||
|
'.png',
|
||||||
|
},
|
||||||
|
|
||||||
|
// cyclosmlite: {
|
||||||
|
// name: 'CyclOSM lite',
|
||||||
|
// minZoom: 0,
|
||||||
|
// maxZoom: 19,
|
||||||
|
// getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
// 'https://' +
|
||||||
|
// getRandomItem(abc) +
|
||||||
|
// '.tile-cyclosm.openstreetmap.fr/cyclosm-lite/' +
|
||||||
|
// zoom +
|
||||||
|
// '/' +
|
||||||
|
// x +
|
||||||
|
// '/' +
|
||||||
|
// y +
|
||||||
|
// '.png',
|
||||||
|
// },
|
||||||
|
// esrisat: {
|
||||||
|
// name: 'ESRI Satellite',
|
||||||
|
// minZoom: 0,
|
||||||
|
// maxZoom: 19,
|
||||||
|
// getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
// 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/' +
|
||||||
|
// zoom +
|
||||||
|
// '/' +
|
||||||
|
// x +
|
||||||
|
// '/' +
|
||||||
|
// y +
|
||||||
|
// '.jpg',
|
||||||
|
// },
|
||||||
|
|
||||||
|
fake: {
|
||||||
|
name: 'Fake provider',
|
||||||
|
minZoom: 0,
|
||||||
|
maxZoom: 20,
|
||||||
|
getTileUrl: (zoom: number, x: number, y: number) =>
|
||||||
|
'https://fakeurl/' + zoom + '/' + x + '/' + y + '.png',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mod = (n: number, m: number) => {
|
||||||
|
const jsMod = n % m;
|
||||||
|
return jsMod >= 0 ? jsMod : jsMod + m;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param keyObject The tile identifier
|
||||||
|
* @returns The `<Tile>`'s URL 'as a string)
|
||||||
|
*/
|
||||||
|
export const getTileUrl = (keyObject: TileKeyObject) => {
|
||||||
|
const nbTiles = 2 ** keyObject.zoomLevel;
|
||||||
|
const x = mod(keyObject.x, nbTiles);
|
||||||
|
const y = mod(keyObject.y, nbTiles);
|
||||||
|
return tileProviders[keyObject.provider].getTileUrl(
|
||||||
|
keyObject.zoomLevel,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default getTileUrl;
|
Loading…
Reference in New Issue