UNPKG

@allmaps/render

Version:

Render functions for WebGL and image buffers

407 lines (406 loc) 15 kB
import { mergeOptions, computeBbox, scaleSize, sizeToResolution, bboxToCenter, bboxToSize, sizeToCenter, sizeToBbox, bboxToRectangle, scalePoint, rotatePoints, sizesToScale, rotatePoint, scalePoints, bufferBboxByRatio, sizeToRectangle, translatePoints, midPoint } from "@allmaps/stdlib"; import { webMercatorProjection, proj4, lonLatProjection } from "@allmaps/project"; import { applyHomogeneousTransform, invertHomogeneousTransform, composeHomogeneousTransform } from "../shared/homogeneous-transform.js"; const defaultViewportOptions = { rotation: 0, devicePixelRatio: 1, projection: webMercatorProjection }; const defaultZoomOptions = { zoom: 1 }; const defaultFitOptions = { fit: "contain" }; class Viewport { geoCenter; geoRectangle; geoSize; geoResolution; geoRectangleBbox; projection; projectedGeoCenter; projectedGeoRectangle; projectedGeoSize; projectedGeoResolution; projectedGeoRectangleBbox; rotation; projectedGeoPerViewportScale; viewportCenter; viewportRectangle; viewportSize; viewportResolution; viewportBbox; devicePixelRatio; canvasCenter; canvasRectangle; canvasSize; canvasResolution; canvasBbox; projectedGeoPerCanvasScale; projectedGeoToViewportHomogeneousTransform = [ 1, 0, 0, 1, 0, 0 ]; projectedGeoToCanvasHomogeneousTransform = [ 1, 0, 0, 1, 0, 0 ]; projectedGeoToClipHomogeneousTransform = [ 1, 0, 0, 1, 0, 0 ]; viewportToClipHomogeneousTransform = [1, 0, 0, 1, 0, 0]; /** * Creates a new Viewport * * @constructor * @param viewportSize - Size of the viewport in viewport pixels, as [width, height]. * @param projectedGeoCenter - Center point of the viewport, in projected geospatial coordinates. * @param projectedGeoPerViewportScale - Scale of the viewport, in projection coordinates per viewport pixel. * @param rotation - Rotation of the viewport with respect to the projected geo coordinate system. Positive values rotate the viewport positively (i.e. counter-clockwise) w.r.t. the map in projected geospatial coordinates. This is equivalent to rotating the map negatively (i.e. clockwise) within the viewport. * @param devicePixelRatio - The devicePixelRatio of the viewport. * @param projection - The projection the projected coordinates are in . */ constructor(viewportSize, projectedGeoCenter, projectedGeoPerViewportScale, partialViewportOptions) { const viewportOptions = mergeOptions( defaultViewportOptions, partialViewportOptions ); this.projectedGeoCenter = projectedGeoCenter; this.projectedGeoPerViewportScale = projectedGeoPerViewportScale; this.rotation = viewportOptions.rotation; this.viewportSize = [ Math.round(viewportSize[0]), Math.round(viewportSize[1]) ]; this.devicePixelRatio = viewportOptions.devicePixelRatio; this.projection = viewportOptions.projection; this.projectedGeoRectangle = this.computeProjectedGeoRectangle( this.viewportSize, this.projectedGeoPerViewportScale, this.rotation, this.projectedGeoCenter ); this.projectedGeoRectangleBbox = computeBbox(this.projectedGeoRectangle); this.projectedGeoSize = scaleSize( this.viewportSize, projectedGeoPerViewportScale ); this.projectedGeoResolution = sizeToResolution(this.projectedGeoSize); this.geoRectangle = this.projectedGeoRectangle.map((point) => { return proj4( this.projection.definition, lonLatProjection.definition, point ); }); this.geoRectangleBbox = computeBbox(this.geoRectangle); this.geoCenter = bboxToCenter(this.geoRectangleBbox); this.geoSize = bboxToSize(this.geoRectangleBbox); this.geoResolution = sizeToResolution(this.geoSize); this.viewportResolution = sizeToResolution(this.viewportSize); this.viewportCenter = sizeToCenter(this.viewportSize); this.viewportBbox = sizeToBbox(this.viewportSize); this.viewportRectangle = bboxToRectangle(this.viewportBbox); this.canvasCenter = scalePoint(this.viewportCenter, this.devicePixelRatio); this.canvasSize = scaleSize(this.viewportSize, this.devicePixelRatio); this.canvasResolution = sizeToResolution(this.canvasSize); this.canvasBbox = sizeToBbox(this.canvasSize); this.canvasRectangle = bboxToRectangle(this.canvasBbox); this.projectedGeoPerCanvasScale = this.projectedGeoPerViewportScale / this.devicePixelRatio; this.projectedGeoToViewportHomogeneousTransform = this.composeProjectedGeoToViewportHomogeneousTransform(); this.projectedGeoToCanvasHomogeneousTransform = this.composeProjectedGeoToCanvasHomogeneousTransform(); this.projectedGeoToClipHomogeneousTransform = this.composeProjectedGeoToClipHomogeneousTransform(); this.viewportToClipHomogeneousTransform = this.composeViewportToClipHomogeneousTransform(); } /** * Static method that creates a Viewport from a size and maps. * * Optionally specify a projection, to be used both when obtaining the extent of selected warped maps in projected geospatial coordinates, as well as when create a viewport * * @param viewportSize - Size of the viewport in viewport pixels, as [width, height]. * @param warpedMapList - A WarpedMapList. * @param partialExtendedViewportOptions - Optional viewport options * @returns A new Viewport object. */ static fromSizeAndMaps(viewportSize, warpedMapList, partialExtendedViewportOptions) { const projectedGeoConvexHull = warpedMapList.getMapsConvexHull( partialExtendedViewportOptions ); if (!projectedGeoConvexHull) { throw new Error( "Maps have no projected convex hull. Possibly because WarpedMapList or Array is empty." ); } return this.fromSizeAndProjectedGeoPolygon( viewportSize, [projectedGeoConvexHull], partialExtendedViewportOptions ); } /** * Static method that creates a Viewport from a size and a polygon in geospatial coordinates, i.e. lon-lat `EPSG:4326`. * * @static * @param viewportSize - Size of the viewport in viewport pixels, as [width, height]. * @param geoPolygon - A polygon in geospatial coordinates. * @param partialExtendedViewportOptions - Optional viewport options * @returns A new Viewport object. */ static fromSizeAndGeoPolygon(viewportSize, geoPolygon, partialExtendedViewportOptions) { const extendedViewportOptions = mergeOptions( { ...defaultViewportOptions, ...defaultZoomOptions, ...defaultFitOptions }, partialExtendedViewportOptions ); const projectedGeoPolygon = geoPolygon.map( (ring) => ring.map( (point) => proj4(extendedViewportOptions.projection.definition, point) ) ); return this.fromSizeAndProjectedGeoPolygon( viewportSize, projectedGeoPolygon, partialExtendedViewportOptions ); } /** * Static method that creates a Viewport from a size and a polygon in projected geospatial coordinates. * * @static * @param viewportSize - Size of the viewport in viewport pixels, as [width, height]. * @param projectedGeoPolygon - A polygon in projected geospatial coordinates. * @param partialExtendedViewportOptions - Optional viewport options * @returns A new Viewport object. */ static fromSizeAndProjectedGeoPolygon(viewportSize, projectedGeoPolygon, partialExtendedViewportOptions) { const extendedViewportOptions = mergeOptions( { ...defaultViewportOptions, ...defaultZoomOptions, ...defaultFitOptions }, partialExtendedViewportOptions ); const projectedGeoRing = projectedGeoPolygon[0]; const rotatedProjectedGeoRing = rotatePoints( projectedGeoRing, -extendedViewportOptions.rotation ); const rotatedProjectedGeoBbox = computeBbox(rotatedProjectedGeoRing); const rotatedProjectedGeoSize = bboxToSize(rotatedProjectedGeoBbox); const rotatedProjectedGeoCenter = bboxToCenter(rotatedProjectedGeoBbox); const projectedGeoPerViewportScale = sizesToScale( rotatedProjectedGeoSize, viewportSize, extendedViewportOptions.fit ); const projectedGeoCenter = rotatePoint( rotatedProjectedGeoCenter, extendedViewportOptions.rotation ); return new Viewport( viewportSize, projectedGeoCenter, projectedGeoPerViewportScale * extendedViewportOptions.zoom, extendedViewportOptions ); } /** * Static method that creates a Viewport from a scale and maps. * * Optionally specify a projection, to be used both when obtaining the extent of selected warped maps in projected geospatial coordinates, as well as when create a viewport * * @param projectedGeoPerViewportScale - Scale of the viewport, in projected geospatial coordinates per viewport pixel. * @param warpedMapList - A WarpedMapList. * @param partialExtendedViewportOptions - Optional viewport options. * @returns A new Viewport object. */ static fromScaleAndMaps(projectedGeoPerViewportScale, warpedMapList, partialExtendedViewportOptions) { const projectedGeoConvexHull = warpedMapList.getMapsConvexHull( partialExtendedViewportOptions ); if (!projectedGeoConvexHull) { throw new Error( "Maps have no projected convex hull. Possibly because WarpedMapList or Array is empty." ); } return this.fromScaleAndProjectedGeoPolygon( projectedGeoPerViewportScale, [projectedGeoConvexHull], partialExtendedViewportOptions ); } /** * Static method that creates a Viewport from a scale and a polygon in geospatial coordinates, i.e. lon-lat `EPSG:4326`. * * Note: the scale is still in *projected* geospatial per viewport pixel! * * @param projectedGeoPerViewportScale - Scale of the viewport, in projected geospatial coordinates per viewport pixel. * @param geoPolygon - A polygon in geospatial coordinates. * @param partialViewportOptions - Optional viewport options. * @returns A new Viewport object. */ static fromScaleAndGeoPolygon(projectedGeoPerViewportScale, geoPolygon, partialExtendedViewportOptions) { const extendedViewportOptions = mergeOptions( { ...defaultViewportOptions, ...defaultZoomOptions, ...defaultFitOptions }, partialExtendedViewportOptions ); const projectedGeoPolygon = geoPolygon.map( (ring) => ring.map( (point) => proj4(extendedViewportOptions.projection.definition, point) ) ); return this.fromScaleAndProjectedGeoPolygon( projectedGeoPerViewportScale, projectedGeoPolygon, partialExtendedViewportOptions ); } /** * Static method that creates a Viewport from a scale and a polygon in projected geospatial coordinates. * * @param projectedGeoPerViewportScale - Scale of the viewport, in projected geospatial coordinates per viewport pixel. * @param projectedGeoPolygon - A polygon in projected geospatial coordinates. * @param partialViewportOptions - Optional viewport options. * @returns A new Viewport object. */ static fromScaleAndProjectedGeoPolygon(projectedGeoPerViewportScale, projectedGeoPolygon, partialExtendedViewportOptions) { const extendedViewportOptions = mergeOptions( { ...defaultViewportOptions, ...defaultZoomOptions }, partialExtendedViewportOptions ); const projectedGeoRing = projectedGeoPolygon[0]; const viewportRing = scalePoints( rotatePoints(projectedGeoRing, -extendedViewportOptions.rotation), 1 / projectedGeoPerViewportScale ); const viewportBbox = computeBbox(viewportRing); const viewportSize = bboxToSize(viewportBbox); const viewportCenter = bboxToCenter(viewportBbox); const projectedGeoCenter = rotatePoint( scalePoint(viewportCenter, projectedGeoPerViewportScale), extendedViewportOptions.rotation ); return new Viewport( viewportSize, projectedGeoCenter, projectedGeoPerViewportScale * extendedViewportOptions.zoom, extendedViewportOptions ); } getProjectedGeoBufferedRectangle(bufferFraction) { const viewportBufferedBbox = bufferBboxByRatio( this.viewportBbox, bufferFraction ); const viewportBufferedRectangle = bboxToRectangle(viewportBufferedBbox); return viewportBufferedRectangle.map( (point) => applyHomogeneousTransform( invertHomogeneousTransform( this.projectedGeoToViewportHomogeneousTransform ), point ) ); } composeProjectedGeoToViewportHomogeneousTransform() { return composeHomogeneousTransform( this.viewportCenter[0], this.viewportCenter[1], 1 / this.projectedGeoPerViewportScale, -1 / this.projectedGeoPerViewportScale, // '-' for handedness -this.rotation, -this.projectedGeoCenter[0], -this.projectedGeoCenter[1] ); } composeProjectedGeoToCanvasHomogeneousTransform() { return composeHomogeneousTransform( this.canvasCenter[0], this.canvasCenter[1], 1 / this.projectedGeoPerCanvasScale, -1 / this.projectedGeoPerCanvasScale, // '-' for handedness -this.rotation, -this.projectedGeoCenter[0], -this.projectedGeoCenter[1] ); } composeProjectedGeoToClipHomogeneousTransform() { return composeHomogeneousTransform( 0, 0, 2 / (this.projectedGeoPerViewportScale * this.viewportSize[0]), 2 / (this.projectedGeoPerViewportScale * this.viewportSize[1]), -this.rotation, -this.projectedGeoCenter[0], -this.projectedGeoCenter[1] ); } composeViewportToClipHomogeneousTransform() { return composeHomogeneousTransform( 0, 0, 2 / this.viewportSize[0], -2 / this.viewportSize[1], // '-' for handedness 0, -this.viewportCenter[0], -this.viewportCenter[1] ); } /** * Returns a rectangle in projected geospatial coordinates * * The rectangle is the result of a horizontal rectangle in Viewport space of size 'viewportSize', * scaled using projectedGeoPerViewportScale, centered, * rotated using 'rotation' and translated to 'projectedGeoCenter'. * * @private * @param viewportSize * @param projectedGeoPerViewportScale * @param rotation * @param projectedGeoCenter */ computeProjectedGeoRectangle(viewportSize, projectedGeoPerViewportScale, rotation, projectedGeoCenter) { const scaled = scaleSize(viewportSize, projectedGeoPerViewportScale); const rectangle = sizeToRectangle(scaled); const centered = translatePoints( rectangle, midPoint(...rectangle), "substract" ); const rotated = rotatePoints(centered, rotation); const translated = translatePoints(rotated, projectedGeoCenter); return translated; } } export { Viewport }; //# sourceMappingURL=Viewport.js.map