mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
77dddd5e8a
Release branch for 4.0.0.
128 lines
5.2 KiB
TypeScript
128 lines
5.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { maxZoomFor, tileView } from '../../../src/components/Studio/mapTiles'
|
|
|
|
/**
|
|
* How sharp a tiled map comes out on paper (#1973).
|
|
*
|
|
* The zoom used to be chosen so the route spanned the frame and nothing more,
|
|
* which put about two tiles across the picture whatever its physical size: a
|
|
* route across Iceland on a 210mm page arrived as 256 by 512 pixels stretched
|
|
* over the sheet. Seventeen dots to the inch, on paper that holds three
|
|
* hundred. It did not look like a bad map, it looked like a screenshot, which
|
|
* is exactly what it was.
|
|
*
|
|
* These cases pin the sum rather than the picture: how many tile pixels end up
|
|
* covering how many millimetres. A change that quietly puts the old behaviour
|
|
* back would show up here as a number falling by a factor of ten.
|
|
*/
|
|
|
|
const OSM = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
|
|
const RELIEF = 'https://gibs.earthdata.nasa.gov/wmts/epsg3857/best'
|
|
+ '/BlueMarble_ShadedRelief_Bathymetry/default/GoogleMapsCompatible_Level8/{z}/{y}/{x}.jpeg'
|
|
|
|
/** A tile is 256 CSS pixels, or 512 when the template asked for retina. */
|
|
const tilePixels = (template: string) => (template.includes('{r}') ? 512 : 256)
|
|
|
|
/** Dots per inch the grid actually delivers over the frame it covers. */
|
|
function dpi(view: { size: number }, template: string): number {
|
|
return tilePixels(template) / (view.size / 25.4)
|
|
}
|
|
|
|
const ICELAND = [
|
|
{ lat: 64.1466, lng: -21.9426 },
|
|
{ lat: 65.6885, lng: -18.1262 },
|
|
{ lat: 64.2539, lng: -15.2082 },
|
|
{ lat: 63.4194, lng: -19.0672 },
|
|
]
|
|
|
|
const BERLIN = [
|
|
{ lat: 52.5163, lng: 13.3777 },
|
|
{ lat: 52.5200, lng: 13.4050 },
|
|
{ lat: 52.5074, lng: 13.3904 },
|
|
]
|
|
|
|
describe('a full page of map', () => {
|
|
it('is drawn at print resolution rather than at screen resolution', () => {
|
|
const view = tileView(ICELAND, { w: 210, h: 297 }, OSM, null)!
|
|
// The old sum landed at 17. Anything in this range is a printable page.
|
|
expect(dpi(view, OSM)).toBeGreaterThan(250)
|
|
})
|
|
|
|
it('spends the tiles on the page it has, not on a fixed grid', () => {
|
|
const small = tileView(ICELAND, { w: 60, h: 60 }, OSM, null)!
|
|
const page = tileView(ICELAND, { w: 210, h: 297 }, OSM, null)!
|
|
// Same route, bigger paper, more pictures. That is the whole idea.
|
|
expect(page.tiles.length).toBeGreaterThan(small.tiles.length)
|
|
})
|
|
|
|
it('stays inside the request budget however large the page is', () => {
|
|
const spread = tileView(ICELAND, { w: 420, h: 297 }, OSM, null)!
|
|
expect(spread.tiles.length).toBeLessThanOrEqual(256)
|
|
})
|
|
|
|
it('asks for the retina tile a template offers, which is free resolution', () => {
|
|
const retina = 'https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
|
|
const view = tileView(BERLIN, { w: 210, h: 210 }, retina, null)!
|
|
expect(view.tiles[0].url).toContain('@2x')
|
|
expect(view.tiles[0].url).not.toContain('{r}')
|
|
})
|
|
|
|
it('leaves a template with no retina placeholder alone', () => {
|
|
const view = tileView(BERLIN, { w: 210, h: 210 }, OSM, null)!
|
|
expect(view.tiles[0].url).not.toContain('@2x')
|
|
})
|
|
})
|
|
|
|
describe('a source with a ceiling', () => {
|
|
/*
|
|
* NASA's imagery stops at level 8. Asking for 9 answers with an error image,
|
|
* which arrives as a page-sized grey rectangle and no explanation.
|
|
*/
|
|
it('never asks NASA for a zoom it does not have', () => {
|
|
expect(maxZoomFor(RELIEF)).toBe(8)
|
|
const view = tileView(BERLIN, { w: 420, h: 297 }, RELIEF, null)!
|
|
expect(view.zoom).toBeLessThanOrEqual(8)
|
|
})
|
|
|
|
it('lets an ordinary tile source go further', () => {
|
|
expect(maxZoomFor(OSM)).toBe(19)
|
|
const relief = tileView(BERLIN, { w: 210, h: 210 }, RELIEF, null)!
|
|
const tiles = tileView(BERLIN, { w: 210, h: 210 }, OSM, null)!
|
|
expect(tiles.zoom).toBeGreaterThan(relief.zoom)
|
|
})
|
|
|
|
it('still honours a zoom the document asked for', () => {
|
|
const view = tileView(ICELAND, { w: 210, h: 297 }, OSM, 6)!
|
|
expect(view.zoom).toBe(6)
|
|
})
|
|
})
|
|
|
|
describe('the credit', () => {
|
|
it('names NASA for its imagery, because a printed book cannot add a footnote later', async () => {
|
|
const { attributionFor } = await import('../../../src/components/Studio/mapTiles')
|
|
expect(attributionFor(RELIEF)).toBe('NASA EOSDIS GIBS')
|
|
})
|
|
})
|
|
|
|
describe('a thumbnail of the same map', () => {
|
|
/*
|
|
* The pages rail and the travel panel draw the same element at about 130px.
|
|
* Cutting those to print resolution would mean a couple of hundred requests
|
|
* for a picture the size of a stamp, every time a panel opens — which
|
|
* against NASA or OpenStreetMap is the bulk fetching their policies ask
|
|
* people not to do.
|
|
*/
|
|
it('costs a handful of tiles rather than a page of them', () => {
|
|
const page = tileView(ICELAND, { w: 210, h: 297 }, OSM, null)!
|
|
const thumb = tileView(ICELAND, { w: 210, h: 297 }, OSM, null, undefined, 'preview')!
|
|
expect(thumb.tiles.length).toBeLessThanOrEqual(16)
|
|
expect(thumb.tiles.length).toBeLessThan(page.tiles.length / 4)
|
|
})
|
|
|
|
it('still covers the same ground, just less finely', () => {
|
|
const thumb = tileView(ICELAND, { w: 210, h: 297 }, OSM, null, undefined, 'preview')!
|
|
expect(thumb.tiles.length).toBeGreaterThan(0)
|
|
expect(thumb.zoom).toBeLessThan(tileView(ICELAND, { w: 210, h: 297 }, OSM, null)!.zoom)
|
|
})
|
|
})
|