UNPKG

@allmaps/render

Version:

Render functions for WebGL and image buffers

579 lines (578 loc) 19.6 kB
import { cloneDeep } from "lodash-es"; import { Image } from "@allmaps/iiif-parser"; import { webMercatorProjection, ProjectedGcpTransformer, lonLatProjection } from "@allmaps/project"; import { mergeOptions, mergeOptionsUnlessUndefined, computeBbox, rectanglesToScale, mergePartialOptions, getPropertyFromCacheOrComputation, mixPoints, fetchImageInfo, bboxToRectangle, sizeToRectangle } from "@allmaps/stdlib"; import { applyHomogeneousTransform } from "../shared/homogeneous-transform.js"; import { WarpedMapEvent, WarpedMapEventType } from "../shared/events.js"; const DEFAULT_PROJECTED_GCP_TRANSFORMER_OPTIONS = { minOffsetRatio: 0.01, minOffsetDistance: 4, maxDepth: 5, differentHandedness: true }; const DEFAULT_WARPED_MAP_OPTIONS = { visible: true, applyMask: true, transformationType: "polynomial", internalProjection: webMercatorProjection, projection: webMercatorProjection }; function createWarpedMapFactory() { return (mapId, georeferencedMap, options) => new WarpedMap(mapId, georeferencedMap, options); } class WarpedMap extends EventTarget { mapId; georeferencedMap; imageInformations; parsedImage; loadingImageInfo; fetchFn; abortController; visible; applyMask; mixed = false; gcps; projectedGcps; resourcePoints; geoPoints; projectedGeoPoints; projectedGeoPreviousTransformedResourcePoints; projectedGeoTransformedResourcePoints; resourceMask; resourceMaskBbox; resourceMaskRectangle; resourceFullMask; resourceFullMaskBbox; resourceFullMaskRectangle; previousTransformationType; transformationType; previousInternalProjection; internalProjection; projection; projectedPreviousTransformer; projectedTransformer; projectedTransformerCache; geoMask; geoMaskBbox; geoMaskRectangle; geoFullMask; geoFullMaskBbox; geoFullMaskRectangle; projectedGeoMask; projectedGeoMaskBbox; projectedGeoMaskRectangle; projectedGeoFullMask; projectedGeoFullMaskBbox; projectedGeoFullMaskRectangle; resourceToProjectedGeoScale; previousDistortionMeasure; distortionMeasure; tileZoomLevelForViewport; overviewTileZoomLevelForViewport; projectedGeoBufferedViewportRectangleForViewport; projectedGeoBufferedViewportRectangleBboxForViewport; resourceBufferedViewportRingForViewport; resourceBufferedViewportRingBboxForViewport; resourceBufferedViewportRingBboxAndResourceMaskBboxIntersectionForViewport; fetchableTilesForViewport = []; overviewFetchableTilesForViewport = []; /** * Creates an instance of WarpedMap. * * @param mapId - ID of the map * @param georeferencedMap - Georeferenced map used to construct the WarpedMap * @param partialWarpedMapOptions - options */ constructor(mapId, georeferencedMap, partialWarpedMapOptions) { super(); const georeferencedMapInput = { transformationType: georeferencedMap.transformation?.type, internalProjection: georeferencedMap.resourceCrs }; const mapAndConstructorOptions = mergeOptions( georeferencedMapInput, partialWarpedMapOptions ); const warpedMapOptions = mergeOptionsUnlessUndefined( DEFAULT_WARPED_MAP_OPTIONS, mapAndConstructorOptions ); this.projectedTransformerCache = /* @__PURE__ */ new Map(); this.mapId = mapId; this.georeferencedMap = georeferencedMap; this.imageInformations = warpedMapOptions.imageInformations; this.loadingImageInfo = false; this.visible = warpedMapOptions.visible; this.applyMask = warpedMapOptions.applyMask; this.fetchFn = warpedMapOptions.fetchFn; this.gcps = this.georeferencedMap.gcps; this.resourceMask = this.applyMask ? this.georeferencedMap.resourceMask : this.getResourceFullMask(); this.updateResourceMaskProperties(); this.updateResourceFullMaskProperties(); this.transformationType = warpedMapOptions.transformationType; this.previousTransformationType = this.transformationType; this.internalProjection = warpedMapOptions.internalProjection; this.previousInternalProjection = this.internalProjection; this.projection = warpedMapOptions.projection; this.updateProjectedTransformerProperties(); } /** * Get resourceMask in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportMask(viewport) { return this.projectedGeoMask.map((point) => { return applyHomogeneousTransform( viewport.projectedGeoToViewportHomogeneousTransform, point ); }); } /** * Get Bbox of resourceMask in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportMaskBbox(viewport) { return computeBbox(this.getViewportMask(viewport)); } /** * Get resourceMaskRectangle in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportMaskRectangle(viewport) { return this.projectedGeoMaskRectangle.map((point) => { return applyHomogeneousTransform( viewport.projectedGeoToViewportHomogeneousTransform, point ); }); } /** * Get resourceFullMask in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportFullMask(viewport) { return this.projectedGeoFullMask.map((point) => { return applyHomogeneousTransform( viewport.projectedGeoToViewportHomogeneousTransform, point ); }); } /** * Get bbox of rresourceFullMask in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportFullMaskBbox(viewport) { return computeBbox(this.getViewportFullMask(viewport)); } /** * Get resourceFullMaskRectangle in viewport coordinates * * @param viewport - the current viewport * @returns */ getViewportFullMaskRectangle(viewport) { return this.projectedGeoFullMaskRectangle.map((point) => { return applyHomogeneousTransform( viewport.projectedGeoToViewportHomogeneousTransform, point ); }); } /** * Get scale of the warped map, in resource pixels per viewport pixels. * * @param viewport - the current viewport * @returns */ getResourceToViewportScale(viewport) { return rectanglesToScale( this.resourceMaskRectangle, this.getViewportMaskRectangle(viewport) ); } /** * Get scale of the warped map, in resource pixels per canvas pixels. * * @param viewport - the current viewport * @returns */ getResourceToCanvasScale(viewport) { return this.getResourceToViewportScale(viewport) / viewport.devicePixelRatio; } /** * Get the reference scaling from the forward transformation of the projected Helmert transformer * * @returns */ getReferenceScale() { const projectedHelmertTransformer = this.getProjectedTransformer("helmert"); const toProjectedGeoHelmertTransformation = projectedHelmertTransformer.getToGeoTransformation(); const helmertMeasures = toProjectedGeoHelmertTransformation.getMeasures(); return helmertMeasures.scale; } /** * Get a projected transformer of the given transformation type. * * Uses cashed projected transformers by transformation type, * and only computes a new projected transformer if none found. * * Returns a projected transformer in the current projection, * even if the cached transformer was computed in a different projection. * * Default settings apply for the options. * * @params transformationType - the transformation type * @params partialProjectedGcpTransformerOptions - options * @params useCache - whether to use the cached projected transformers previously computed * @returns A projected transformer */ getProjectedTransformer(transformationType, partialProjectedGcpTransformerOptions) { partialProjectedGcpTransformerOptions = mergePartialOptions( { projection: this.projection, internalProjection: this.internalProjection }, partialProjectedGcpTransformerOptions ); partialProjectedGcpTransformerOptions = mergePartialOptions( DEFAULT_PROJECTED_GCP_TRANSFORMER_OPTIONS, partialProjectedGcpTransformerOptions ); const projectedTransformer = getPropertyFromCacheOrComputation( this.projectedTransformerCache, transformationType, () => new ProjectedGcpTransformer( this.gcps, transformationType, partialProjectedGcpTransformerOptions ) ); return projectedTransformer.setProjection(this.projection); } /** * Update the ground control points loaded from a georeferenced map to new ground control points. * * @param gcps */ setGcps(gcps) { this.gcps = gcps; this.clearProjectedTransformerCaches(); this.updateProjectedTransformerProperties(); this.updateGcpsProperties(); } /** * Update the resource mask loaded from a georeferenced map to a new mask. * * @param resourceMask */ setResourceMask(resourceMask) { this.applyMask = true; this.resourceMask = resourceMask; this.updateResourceMaskProperties(); this.updateResourceFullMaskProperties(); this.updateGeoMaskProperties(); this.updateProjectedGeoMaskProperties(); } /** * Set the transformationType * * @param transformationType */ setTransformationType(transformationType) { this.transformationType = transformationType; this.updateProjectedTransformerProperties(); } /** * Set the distortionMeasure * * @param distortionMeasure - the disortion measure */ setDistortionMeasure(distortionMeasure) { this.distortionMeasure = distortionMeasure; } /** * Set the internal projection * * @param projection - the internal projection */ setInternalProjection(projection) { this.internalProjection = projection || DEFAULT_PROJECTED_GCP_TRANSFORMER_OPTIONS.internalProjection || webMercatorProjection; this.clearProjectedTransformerCaches(); this.updateProjectedTransformerProperties(); } /** * Set the projection * * @param projection - the projection */ setProjection(projection) { this.projection = projection || DEFAULT_PROJECTED_GCP_TRANSFORMER_OPTIONS.projection || webMercatorProjection; this.updateProjectedTransformerProperties(); } /** * Set the tile zoom level for the current viewport * * @param tileZoomLevel - tile zoom level for the current viewport */ setTileZoomLevelForViewport(tileZoomLevel) { this.tileZoomLevelForViewport = tileZoomLevel; } /** * Set the overview tile zoom level for the current viewport * * @param tileZoomLevel - tile zoom level for the current viewport */ setOverviewTileZoomLevelForViewport(tileZoomLevel) { this.overviewTileZoomLevelForViewport = tileZoomLevel; } /** * Set projectedGeoBufferedViewportRectangle for the current viewport * * @param projectedGeoBufferedViewportRectangle */ setProjectedGeoBufferedViewportRectangleForViewport(projectedGeoBufferedViewportRectangle) { this.projectedGeoBufferedViewportRectangleForViewport = projectedGeoBufferedViewportRectangle; this.projectedGeoBufferedViewportRectangleBboxForViewport = projectedGeoBufferedViewportRectangle ? computeBbox(projectedGeoBufferedViewportRectangle) : void 0; } /** * Set resourceBufferedViewportRing for the current viewport * * @param resourceBufferedViewportRing */ setResourceBufferedViewportRingForViewport(resourceBufferedViewportRing) { this.resourceBufferedViewportRingForViewport = resourceBufferedViewportRing; this.resourceBufferedViewportRingBboxForViewport = resourceBufferedViewportRing ? computeBbox(resourceBufferedViewportRing) : void 0; } /** * Set resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection for the current viewport * * @param resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection */ setResourceBufferedViewportRingBboxAndResourceMaskBboxIntersectionForViewport(resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection) { this.resourceBufferedViewportRingBboxAndResourceMaskBboxIntersectionForViewport = resourceBufferedViewportRingBboxAndResourceMaskBboxIntersection; } /** * Set tiles for the current viewport * * @param fetchableTiles */ setFetchableTilesForViewport(fetchableTiles) { this.fetchableTilesForViewport = fetchableTiles; } /** * Set overview tiles for the current viewport * * @param overviewFetchableTiles */ setOverviewFetchableTilesForViewport(overviewFetchableTiles) { this.overviewFetchableTilesForViewport = overviewFetchableTiles; } /** * Reset the properties for the current values */ resetForViewport() { this.setTileZoomLevelForViewport(); this.setOverviewTileZoomLevelForViewport(); this.setProjectedGeoBufferedViewportRectangleForViewport(); this.setResourceBufferedViewportRingForViewport(); this.setFetchableTilesForViewport([]); this.setOverviewFetchableTilesForViewport([]); } /** * Reset previous transform properties to new ones (when completing a transformer transitions). */ resetPrevious() { this.mixed = false; this.previousTransformationType = this.transformationType; this.previousDistortionMeasure = this.distortionMeasure; this.previousInternalProjection = this.internalProjection; this.projectedPreviousTransformer = cloneDeep(this.projectedTransformer); this.projectedGeoPreviousTransformedResourcePoints = this.projectedGeoTransformedResourcePoints; } /** * Mix previous transform properties with new ones (when changing an ongoing transformer transition). * * @param t - animation progress */ mixPreviousAndNew(t) { this.mixed = true; this.previousTransformationType = this.transformationType; this.previousDistortionMeasure = this.distortionMeasure; this.previousInternalProjection = this.internalProjection; this.projectedPreviousTransformer = cloneDeep(this.projectedTransformer); this.projectedGeoPreviousTransformedResourcePoints = this.projectedGeoTransformedResourcePoints.map((point, index) => { return mixPoints( point, this.projectedGeoPreviousTransformedResourcePoints[index], t ); }); } /** * Check if this instance has image info * * @returns */ hasImageInfo() { return this.parsedImage !== void 0; } /** * Fetch and parse the image info, and generate the image ID * * @returns */ async loadImageInfo() { try { this.loadingImageInfo = true; const imageUri = this.georeferencedMap.resource.id; let imageInfo; if (this.imageInformations?.get(imageUri)) { imageInfo = this.imageInformations.get(imageUri); } else { this.abortController = new AbortController(); const signal = this.abortController.signal; imageInfo = await fetchImageInfo(imageUri, { signal }, this.fetchFn); this.abortController = void 0; this.imageInformations?.set(imageUri, imageInfo); } this.parsedImage = Image.parse(imageInfo); this.dispatchEvent(new WarpedMapEvent(WarpedMapEventType.IMAGEINFOLOADED)); } catch (err) { this.loadingImageInfo = false; throw err; } finally { this.loadingImageInfo = false; } } updateResourceMaskProperties() { this.resourceMaskBbox = computeBbox(this.resourceMask); this.resourceMaskRectangle = bboxToRectangle(this.resourceMaskBbox); } getResourceFullMask() { const resourceWidth = this.georeferencedMap.resource.width; const resourceHeight = this.georeferencedMap.resource.height; if (resourceWidth && resourceHeight) { return sizeToRectangle([resourceWidth, resourceHeight]); } else { return bboxToRectangle(this.resourceMaskBbox); } } updateResourceFullMaskProperties() { this.resourceFullMask = this.getResourceFullMask(); this.resourceFullMaskBbox = computeBbox(this.resourceFullMask); this.resourceFullMaskRectangle = bboxToRectangle(this.resourceFullMaskBbox); } updateGeoMaskProperties() { this.updateGeoMask(); this.updateFullGeoMask(); } updateProjectedGeoMaskProperties() { this.updateProjectedGeoMask(); this.updateProjectedFullGeoMask(); this.updateResourceToProjectedGeoScale(); } updateProjectedTransformerProperties() { this.updateProjectedTransformer(); this.updateGeoMaskProperties(); this.updateProjectedGeoMaskProperties(); this.updateGcpsProperties(); } updateProjectedTransformer() { this.projectedTransformer = this.getProjectedTransformer( this.transformationType ); if (!this.projectedPreviousTransformer) { this.projectedPreviousTransformer = this.projectedTransformer; } } updateGeoMask() { this.geoMask = this.projectedTransformer.transformToGeo( [this.resourceMask], { projection: lonLatProjection } )[0]; this.geoMaskBbox = computeBbox(this.geoMask); this.geoMaskRectangle = this.projectedTransformer.transformToGeo( [this.resourceMaskRectangle], { maxDepth: 0, projection: lonLatProjection } )[0]; } updateFullGeoMask() { this.geoFullMask = this.projectedTransformer.transformToGeo( [this.resourceFullMask], { projection: lonLatProjection } )[0]; this.geoFullMaskBbox = computeBbox(this.geoFullMask); this.geoFullMaskRectangle = this.projectedTransformer.transformToGeo( [this.resourceFullMaskRectangle], { maxDepth: 0, projection: lonLatProjection } )[0]; } updateProjectedGeoMask() { this.projectedGeoMask = this.projectedTransformer.transformToGeo([ this.resourceMask ])[0]; this.projectedGeoMaskBbox = computeBbox(this.projectedGeoMask); this.projectedGeoMaskRectangle = this.projectedTransformer.transformToGeo( [this.resourceMaskRectangle], { maxDepth: 0 } )[0]; } updateProjectedFullGeoMask() { this.projectedGeoFullMask = this.projectedTransformer.transformToGeo([ this.resourceFullMask ])[0]; this.projectedGeoFullMaskBbox = computeBbox(this.projectedGeoFullMask); this.projectedGeoFullMaskRectangle = this.projectedTransformer.transformToGeo( [this.resourceFullMaskRectangle], { maxDepth: 0 } )[0]; } updateResourceToProjectedGeoScale() { this.resourceToProjectedGeoScale = rectanglesToScale( this.resourceMaskRectangle, this.projectedGeoMaskRectangle ); } updateGcpsProperties() { this.projectedGcps = this.gcps.map(({ resource, geo }) => ({ resource, geo: this.projectedTransformer.lonLatToProjection(geo) })); this.resourcePoints = this.gcps.map((gcp) => gcp.resource); this.geoPoints = this.gcps.map((gcp) => gcp.geo); this.projectedGeoPoints = this.projectedGcps.map( (projectedGcp) => projectedGcp.geo ); this.projectedGeoTransformedResourcePoints = this.gcps.map( (projectedGcp) => this.projectedTransformer.transformToGeo(projectedGcp.resource) ); if (!this.projectedGeoPreviousTransformedResourcePoints) { this.projectedGeoPreviousTransformedResourcePoints = this.projectedGeoTransformedResourcePoints; } } clearProjectedTransformerCaches() { this.projectedTransformerCache = /* @__PURE__ */ new Map(); } destroy() { if (this.abortController) { this.abortController.abort(); } } } export { WarpedMap, createWarpedMapFactory }; //# sourceMappingURL=WarpedMap.js.map