@allmaps/render
Version:
Render functions for WebGL and image buffers
532 lines (531 loc) • 19.3 kB
JavaScript
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