UNPKG

@allmaps/render

Version:

Render functions for WebGL and image buffers

532 lines (531 loc) 19.3 kB
import { TileCache } from "../tilecache/TileCache.js"; import { WarpedMapList } from "../maps/WarpedMapList.js"; import { FetchableTile } from "../tilecache/FetchableTile.js"; import { WarpedMapEvent, WarpedMapEventType } from "../shared/events.js"; import { getTileZoomLevelForScale, computeTilesCoveringRingAtTileZoomLevel, squaredDistanceTileToPoint, getTilesResolution, getTileZoomLevelResolution, getTilesAtScaleFactor } from "../shared/tiles.js"; import { mergeOptions, computeBbox, squaredDistance, bboxToCenter, intersectBboxes, bboxToRectangle } from "@allmaps/stdlib"; import { proj4, lonLatProjection } from "@allmaps/project"; const defaultBaseRendererOptions = {}; const REQUEST_VIEWPORT_BUFFER_RATIO = 0; const OVERVIEW_REQUEST_VIEWPORT_BUFFER_RATIO = 2; const PRUNE_VIEWPORT_BUFFER_RATIO = 4; const OVERVIEW_PRUNE_VIEWPORT_BUFFER_RATIO = 10; const SCALE_FACTOR_CORRECTION = 0; const LOG2_SCALE_FACTOR_CORRECTION = 0.4; const MAX_MAP_OVERVIEW_RESOLUTION = 1024 * 1024; const MAX_TOTAL_RESOLUTION_RATIO = 10; const MAX_GCPS_EXACT_TPS_TO_RESOURCE = 100; class BaseRenderer extends EventTarget { partialBaseRendererOptions; warpedMapList; tileCache; mapsInPreviousViewport = /* @__PURE__ */ new Set(); mapsInViewport = /* @__PURE__ */ new Set(); mapsWithRequestedTilesForViewport = /* @__PURE__ */ new Set(); viewport; constructor(cachableTileFactory, warpedMapFactory, partialBaseRendererOptions) { super(); this.partialBaseRendererOptions = mergeOptions( defaultBaseRendererOptions, partialBaseRendererOptions ); this.tileCache = new TileCache( cachableTileFactory, partialBaseRendererOptions ); this.warpedMapList = new WarpedMapList( warpedMapFactory, partialBaseRendererOptions ); } /** * Parses an annotation and adds its georeferenced map to this renderer's warped map list * * @param annotation * @returns */ async addGeoreferenceAnnotation(annotation) { return this.warpedMapList.addGeoreferenceAnnotation(annotation); } /** * Adds a georeferenced map to this renderer's warped map list * * @param georeferencedMap * @returns */ async addGeoreferencedMap(georeferencedMap) { return this.warpedMapList.addGeoreferencedMap(georeferencedMap); } /** * Set the Base Renderer options * * @param partialBaseRendererOptions - Options */ setOptions(partialBaseRendererOptions) { this.partialBaseRendererOptions = mergeOptions( this.partialBaseRendererOptions, partialBaseRendererOptions ); this.tileCache.setOptions(partialBaseRendererOptions); this.warpedMapList.setOptions(partialBaseRendererOptions); } loadMissingImageInfosInViewport() { if (!this.viewport) { return []; } return Array.from( this.warpedMapList.getWarpedMaps({ geoBbox: this.viewport.geoRectangleBbox }) ).filter( (warpedMap) => !warpedMap.hasImageInfo() && !warpedMap.loadingImageInfo ).map((warpedMap) => warpedMap.loadImageInfo()); } someImageInfosInViewport() { if (!this.viewport) { return false; } return Array.from( this.findMapsInViewport( this.shouldAnticipateInteraction() ? OVERVIEW_PRUNE_VIEWPORT_BUFFER_RATIO : 0 ) ).map((mapId) => this.warpedMapList.getWarpedMap(mapId)).map((warpedMap) => warpedMap.hasImageInfo()).some(Boolean); } shouldRequestFetchableTiles() { return true; } // Should we anticipate user interaction (panning or zooming) // and hence buffer the viewport or get overview tiles shouldAnticipateInteraction() { return false; } assureProjection() { if (!this.viewport) { return; } this.warpedMapList.partialWarpedMapListOptions.projection = this.viewport.projection; this.warpedMapList.setMapsProjection(this.viewport.projection, { onlyVisible: false }); } requestFetchableTiles() { if (!this.shouldRequestFetchableTiles()) { return; } const fetchableTilesForViewport = []; const overviewFetchableTilesForViewport = []; const mapsInViewportForRequest = this.findMapsInViewport( this.shouldAnticipateInteraction() ? REQUEST_VIEWPORT_BUFFER_RATIO : 0 ); const mapsInViewportForOverviewRequest = this.findMapsInViewport( this.shouldAnticipateInteraction() ? OVERVIEW_REQUEST_VIEWPORT_BUFFER_RATIO : 0 ); const mapsInViewportForPrune = this.findMapsInViewport( this.shouldAnticipateInteraction() ? PRUNE_VIEWPORT_BUFFER_RATIO : 0 ); const mapsInViewportForOverviewPrune = this.findMapsInViewport( this.shouldAnticipateInteraction() ? OVERVIEW_PRUNE_VIEWPORT_BUFFER_RATIO : 0 ); for (const warpedMap of this.warpedMapList.getWarpedMaps()) { warpedMap.resetForViewport(); } for (const mapId of mapsInViewportForPrune) { fetchableTilesForViewport.push( ...this.getMapFetchableTilesForViewport(mapId, mapsInViewportForRequest) ); } if (this.shouldAnticipateInteraction()) { for (const mapId of mapsInViewportForOverviewPrune) { overviewFetchableTilesForViewport.push( ...this.getMapOverviewFetchableTilesForViewport( mapId, [ ...fetchableTilesForViewport, ...overviewFetchableTilesForViewport ], mapsInViewportForOverviewRequest ) ); } } this.tileCache.requestFetchableTiles([ ...fetchableTilesForViewport, ...overviewFetchableTilesForViewport ]); this.updateMapsForViewport([ ...fetchableTilesForViewport, ...overviewFetchableTilesForViewport ]); this.pruneTileCache(mapsInViewportForOverviewPrune); } findMapsInViewport(viewportBufferRatio = 0) { if (!this.viewport) { return /* @__PURE__ */ new Set(); } const viewport = this.viewport; const projectedGeoBufferedRectangle = this.viewport.getProjectedGeoBufferedRectangle(viewportBufferRatio); const geoBufferedRectangleBbox = computeBbox( projectedGeoBufferedRectangle.map( (point) => proj4( viewport.projection.definition, lonLatProjection.definition, point ) ) ); return new Set( Array.from( this.warpedMapList.getWarpedMaps({ geoBbox: geoBufferedRectangleBbox }) ).sort((warpedMapA, warpedMapB) => { if (warpedMapA && warpedMapB) { return squaredDistance( bboxToCenter(warpedMapA.projectedGeoMaskBbox), viewport.projectedGeoCenter ) - squaredDistance( bboxToCenter(warpedMapB.projectedGeoMaskBbox), viewport.projectedGeoCenter ); } else { return 0; } }).map((warpedMap) => warpedMap.mapId) ); } getMapFetchableTilesForViewport(mapId, mapsInViewportForRequest) { if (!this.viewport) { return []; } const viewport = this.viewport; const warpedMap = this.warpedMapList.getWarpedMap(mapId); if (!warpedMap) { return []; } if (!warpedMap.visible) { return []; } if (!warpedMap.hasImageInfo()) { return []; } const tileZoomLevel = getTileZoomLevelForScale( warpedMap.parsedImage.tileZoomLevels, warpedMap.getResourceToCanvasScale(viewport), SCALE_FACTOR_CORRECTION, LOG2_SCALE_FACTOR_CORRECTION ); warpedMap.setTileZoomLevelForViewport(tileZoomLevel); const transformerOptions = { maxDepth: 0, // maxDepth: 2, // minOffsetRatio: 0.00001, sourceIsGeographic: false, destinationIsGeographic: true }; const projectedGeoBufferedViewportRectangle = viewport.getProjectedGeoBufferedRectangle( this.shouldAnticipateInteraction() ? REQUEST_VIEWPORT_BUFFER_RATIO : 0 ); const projectedTransformer = warpedMap.transformationType === "thinPlateSpline" && warpedMap.gcps.length > MAX_GCPS_EXACT_TPS_TO_RESOURCE ? warpedMap.getProjectedTransformer("polynomial") : warpedMap.projectedTransformer; const resourceBufferedViewportRing = projectedTransformer.transformToResource( [projectedGeoBufferedViewportRectangle], transformerOptions )[0]; warpedMap.setProjectedGeoBufferedViewportRectangleForViewport( projectedGeoBufferedViewportRectangle ); warpedMap.setResourceBufferedViewportRingForViewport( resourceBufferedViewportRing ); if (!warpedMap.resourceBufferedViewportRingBboxForViewport || !warpedMap.resourceBufferedViewportRingBboxForViewport) { throw new Error( "No resourceBufferedViewportRingBboxForViewport or resourceBufferedViewportRingBboxForViewport" ); } const resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection = intersectBboxes( warpedMap.resourceBufferedViewportRingBboxForViewport, warpedMap.resourceMaskBbox ); warpedMap.setResourceBufferedViewportRingBboxAndResourceMaskBboxIntersectionForViewport( resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection ); if (!mapsInViewportForRequest.has(mapId)) { return []; } if (!resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection) { return []; } const tiles = computeTilesCoveringRingAtTileZoomLevel( bboxToRectangle( resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection ), tileZoomLevel, [warpedMap.parsedImage.width, warpedMap.parsedImage.height] ); const resourceBufferedViewportRingCenter = bboxToCenter( warpedMap.resourceBufferedViewportRingBboxForViewport ); tiles.sort( (tileA, tileB) => squaredDistanceTileToPoint(tileA, resourceBufferedViewportRingCenter) - squaredDistanceTileToPoint(tileB, resourceBufferedViewportRingCenter) ); const fetchableTiles = tiles.map( (tile) => new FetchableTile(tile, warpedMap) ); warpedMap.setFetchableTilesForViewport(fetchableTiles); return fetchableTiles; } getMapOverviewFetchableTilesForViewport(mapId, totalFetchableTilesForViewport, mapsInViewportForOverviewRequest) { if (!this.viewport) { return []; } const warpedMap = this.warpedMapList.getWarpedMap(mapId); if (!warpedMap) { return []; } if (!warpedMap.visible) { return []; } if (!warpedMap.hasImageInfo()) { return []; } const totalFetchableTilesResolution = getTilesResolution( totalFetchableTilesForViewport.map((fetchableTile) => fetchableTile.tile) ); const maxTotalFetchableTilesResolution = this.viewport.canvasResolution * MAX_TOTAL_RESOLUTION_RATIO; if (totalFetchableTilesResolution > maxTotalFetchableTilesResolution) { return []; } const overviewTileZoomLevel = warpedMap.parsedImage.tileZoomLevels.filter( (tileZoomLevel) => getTileZoomLevelResolution(tileZoomLevel) <= MAX_MAP_OVERVIEW_RESOLUTION // Neglect zoomlevels that contain too many pixels ).sort( (tileZoomLevel0, tileZoomLevel1) => tileZoomLevel0.scaleFactor - tileZoomLevel1.scaleFactor // Enforcing default ascending order, e.g. from 1 to 16 ).at(-1); warpedMap.setOverviewTileZoomLevelForViewport(overviewTileZoomLevel); if (!mapsInViewportForOverviewRequest.has(mapId)) { return []; } if (!overviewTileZoomLevel || warpedMap.tileZoomLevelForViewport && overviewTileZoomLevel.scaleFactor <= warpedMap.tileZoomLevelForViewport.scaleFactor) { return []; } const overviewTiles = getTilesAtScaleFactor( overviewTileZoomLevel.scaleFactor, warpedMap.parsedImage ); const overviewFetchableTiles = overviewTiles.map( (tile) => new FetchableTile(tile, warpedMap) ); warpedMap.setOverviewFetchableTilesForViewport(overviewFetchableTiles); return overviewFetchableTiles; } updateMapsForViewport(tiles) { this.mapsWithRequestedTilesForViewport = new Set( tiles.map((tile) => tile.mapId).filter((v, i, a) => { return a.indexOf(v) === i; }).sort((mapIdA, mapIdB) => { const zIndexA = this.warpedMapList.getMapZIndex(mapIdA); const zIndexB = this.warpedMapList.getMapZIndex(mapIdB); if (zIndexA !== void 0 && zIndexB !== void 0) { return zIndexA - zIndexB; } return 0; }) ); this.mapsInPreviousViewport = this.mapsInViewport; this.mapsInViewport = this.findMapsInViewport(); const mapsInPreviousViewportAsArray = Array.from( this.mapsInPreviousViewport ); const mapsInViewportAsArray = Array.from(this.mapsInViewport); const mapsEnteringViewport = mapsInViewportAsArray.filter( (mapId) => !mapsInPreviousViewportAsArray.includes(mapId) ); const mapsLeavingViewport = mapsInPreviousViewportAsArray.filter( (mapId) => !mapsInViewportAsArray.includes(mapId) ); for (const mapId of mapsEnteringViewport) { this.dispatchEvent( new WarpedMapEvent(WarpedMapEventType.WARPEDMAPENTER, mapId) ); } for (const mapId of mapsLeavingViewport) { this.clearMap(mapId); this.dispatchEvent( new WarpedMapEvent(WarpedMapEventType.WARPEDMAPLEAVE, mapId) ); } return { mapsEnteringViewport, mapsLeavingViewport }; } pruneTileCache(mapsInViewportForOverviewPrune) { const pruneInfoByMapId = /* @__PURE__ */ new Map(); for (const warpedMap of this.warpedMapList.getWarpedMaps({ mapIds: mapsInViewportForOverviewPrune })) { pruneInfoByMapId.set(warpedMap.mapId, { tileZoomLevelForViewport: warpedMap.tileZoomLevelForViewport, overviewTileZoomLevelForViewport: warpedMap.overviewTileZoomLevelForViewport, resourceViewportRingBboxForViewport: warpedMap.resourceBufferedViewportRingBboxForViewport }); } this.tileCache.prune(pruneInfoByMapId); } destroy() { this.tileCache.destroy(); this.warpedMapList.destroy(); } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars clearMap(mapId) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars mapTileLoaded(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars mapTileRemoved(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars imageInfoLoaded(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars warpedMapAdded(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars warpedMapRemoved(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars preChange(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars optionsChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars gcpsChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars resourceMaskChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars transformationChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars distortionChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars internalProjectionChanged(event) { } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars projectionChanged(event) { } addEventListeners() { this.tileCache.addEventListener( WarpedMapEventType.MAPTILELOADED, this.mapTileLoaded.bind(this) ); this.tileCache.addEventListener( WarpedMapEventType.MAPTILEREMOVED, this.mapTileRemoved.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.IMAGEINFOLOADED, this.imageInfoLoaded.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.WARPEDMAPADDED, this.warpedMapAdded.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.WARPEDMAPREMOVED, this.warpedMapRemoved.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.PRECHANGE, this.preChange.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.OPTIONSCHANGED, this.optionsChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.GCPSCHANGED, this.gcpsChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.RESOURCEMASKCHANGED, this.gcpsChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.TRANSFORMATIONCHANGED, this.transformationChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.DISTORTIONCHANGED, this.distortionChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.INTERNALPROJECTIONCHANGED, this.internalProjectionChanged.bind(this) ); this.warpedMapList.addEventListener( WarpedMapEventType.PROJECTIONCHANGED, this.projectionChanged.bind(this) ); } removeEventListeners() { this.tileCache.removeEventListener( WarpedMapEventType.MAPTILELOADED, this.mapTileLoaded.bind(this) ); this.tileCache.removeEventListener( WarpedMapEventType.MAPTILEREMOVED, this.mapTileRemoved.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.IMAGEINFOLOADED, this.imageInfoLoaded.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.WARPEDMAPADDED, this.warpedMapAdded.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.WARPEDMAPREMOVED, this.warpedMapRemoved.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.PRECHANGE, this.preChange.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.OPTIONSCHANGED, this.optionsChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.GCPSCHANGED, this.gcpsChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.RESOURCEMASKCHANGED, this.gcpsChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.TRANSFORMATIONCHANGED, this.transformationChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.DISTORTIONCHANGED, this.distortionChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.INTERNALPROJECTIONCHANGED, this.internalProjectionChanged.bind(this) ); this.warpedMapList.removeEventListener( WarpedMapEventType.PROJECTIONCHANGED, this.projectionChanged.bind(this) ); } } export { BaseRenderer }; //# sourceMappingURL=BaseRenderer.js.map