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