mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user