UNPKG

@allmaps/transform

Version:

Coordinate transformation functions

229 lines (228 loc) 12.8 kB
import { geojsonGeometryToGeometry, geojsonGeometriesToGeojsonFeatureCollection, geojsonFeatureCollectionToGeojsonGeometries, geometryToGeojsonGeometry, geometryToSvgGeometry, stringToSvgGeometriesGenerator, svgGeometriesToSvgString, svgGeometryToGeometry, mergePartialOptions, mergeOptions } from '@allmaps/stdlib'; import { BaseGcpTransformer } from './BaseGcpTransformer.js'; import { gcpTransformerOptionsToGeneralGcpTransformerOptions, gcpTransformOptionsToGeneralGcpTransformOptions } from '../shared/transform-functions.js'; import { gcpToGeneralGcp, gcpToPointForToGeo, gcpToPointForToResource, generalGcpToGcp } from '../shared/conversion-functions.js'; /** * Class for Ground Control Point Transformers. * * A GCP Transformer can transform input geometries between 'resource' and 'geo' spaces. * * It does this using a transformation (of a certain type) built from * a set of Ground Control Points (with coordinates in * resource and geo space) and a transformation type. * * It has a method to transform 'toGeo' (from resource to geo space) * and 'toResource' (from geo to resource space), * * This class differs from the GeneralGcpTransformer class in that * it specifically handles the typical Allmaps case where: * - We read in Ground Control Points using the Gcp type, with `resource` and `geo` fields. * - We use the terms 'toGeo' for the 'forward' transformation and 'toResource' for the 'backward' transformation. * - The `differentHandedness` setting is `true` by default, since we expect the resource coordinates to identify pixels on an image, with origin in the top left and the y-axis pointing down. * */ export class GcpTransformer extends BaseGcpTransformer { /** * Create a GcpTransformer * * @param gcps - An array of Ground Control Points (GCPs) * @param type - The transformation type * @param partialGcpTransformerOptions - GCP Transformer options */ constructor(gcps, type = 'polynomial', partialGcpTransformerOptions) { const generalGcps = gcps // Allow incomplete GCPs, but filter them out .filter((gcp) => gcp.geo && gcp.resource) .map(gcpToGeneralGcp); partialGcpTransformerOptions = mergePartialOptions({ differentHandedness: true }, partialGcpTransformerOptions); super(generalGcps, type, gcpTransformerOptionsToGeneralGcpTransformerOptions(partialGcpTransformerOptions)); } /** * Get GCPs as they were inputed to the GCP Transformer. */ get gcps() { return this.generalGcpsInternal.map(generalGcpToGcp); } /** * Get the forward transformation. Create if it doesn't exist yet. */ getToGeoTransformation() { return super.getForwardTransformationInternal(); } /** * Get the backward transformation. Create if it doesn't exist yet. */ getToResourceTransformation() { return super.getBackwardTransformationInternal(); } /** * Get the resolution of the toGeo transformation in resource space, within a given bbox. * * This informs you in how fine the warping is, in resource space. * It can be useful e.g. to create a triangulation in resource space * that is fine enough for this warping. * * It is obtained by transforming toGeo two linestring, * namely the horizontal and vertical midlines of the given bbox. * The toGeo transformation will refine these lines: * it will break them in small enough pieces to obtain a near continuous result. * Returned in the length of the shortest piece, measured in resource coordinates. * * @param resourceBbox - BBox in resource space where the resolution is requested * @param partialGcpTransformOptions - GCP Transform options to consider during the transformation * @returns Resolution of the toGeo transformation in resource space */ getToGeoTransformationResolution(resourceBbox, partialGcpTransformOptions) { const partialGeneralGcpTransformOptions = gcpTransformOptionsToGeneralGcpTransformOptions(partialGcpTransformOptions); return super.getForwardTransformationResolutionInternal(resourceBbox, partialGeneralGcpTransformOptions); } /** * Get the resolution of the toResource transformation in geo space, within a given bbox. * * This informs you in how fine the warping is, in geo space. * It can be useful e.g. to create a triangulation in geo space * that is fine enough for this warping. * * It is obtained by transforming toResource two linestring, * namely the horizontal and vertical midlines of the given bbox. * The toResource transformation will refine these lines: * it will break them in small enough pieces to obtain a near continuous result. * Returned in the length of the shortest piece, measured in geo coordinates. * * @param geoBbox - BBox in geo space where the resolution is requested * @param partialGcpTransformOptions - GCP Transform options to consider during the transformation * @returns Resolution of the toResource transformation in geo space */ getToResourceTransformationResolution(geoBbox, partialGcpTransformOptions) { const generalGcpTransformOptions = gcpTransformOptionsToGeneralGcpTransformOptions(partialGcpTransformOptions); return super.getBackwardTransformationResolutionInternal(geoBbox, generalGcpTransformOptions); } /** * Get the transformer options. */ getTransformerOptions() { return this.transformerOptions; } /** * Set the transformer options. * * Use with caution, especially for options that have effects in the constructor. */ setTransformerOptionsInternal(partialGcpTransformerOptions) { this.transformerOptions = mergeOptions(this.transformerOptions, gcpTransformerOptionsToGeneralGcpTransformerOptions(partialGcpTransformerOptions)); } /** * Transform a geometry to geo space * * @param geometry - Geometry to transform * @param partialGcpTransformOptions - GCP Transform options * @param gcpToP - Return type function * @returns Input geometry transformed to geo space */ transformToGeo(geometry, partialGcpTransformOptions, gcpToP = gcpToPointForToGeo) { const generalGcpToP = (generalGcp) => gcpToP(generalGcpToGcp(generalGcp)); const partialGeneralGcpTransformOptions = partialGcpTransformOptions ? gcpTransformOptionsToGeneralGcpTransformOptions(partialGcpTransformOptions) : undefined; return super.transformForwardInternal(geometry, partialGeneralGcpTransformOptions, generalGcpToP); } /** * Transform a geometry to resource space * * @param geometry - Geometry to transform * @param partialGcpTransformOptions - GCP Transform options * @param gcpToP - Return type function * @returns Input geometry transformed to resource space */ transformToResource(geometry, partialGcpTransformOptions, gcpToP = gcpToPointForToResource) { const generalGcpToP = (generalGcp) => gcpToP(generalGcpToGcp(generalGcp)); const partialGeneralGcpTransformOptions = partialGcpTransformOptions ? gcpTransformOptionsToGeneralGcpTransformOptions(partialGcpTransformOptions) : undefined; return super.transformBackwardInternal(geometry, partialGeneralGcpTransformOptions, generalGcpToP); } /** * Transform an SVG geometry to geo space as a GeoJSON Geometry * * This is a shortcut method, available as static method in order not to overpopulate intellisense suggestions * Note: since this converts to GeoJSON we assume geo-space is in lon-lat WGS84 and automatically set `destinationIsGeographic` to use geographically computed midpoints. * Note: Multi-geometries are not supported * * @param transformer - A GCP Transformer defining the transformation * @param geometry - SVG geometry to transform * @param partialGcpTransformOptions - GCP Transform options * @returns Input SVG geometry transformed to geo space, as a GeoJSON Geometry */ static transformSvgToGeojson(transformer, svgGeometry, partialGcpTransformOptions) { partialGcpTransformOptions = mergePartialOptions({ geoIsGeographic: true }, partialGcpTransformOptions); // This middle step is needed to make typescript happy const transformedGeometry = transformer.transformToGeo(svgGeometryToGeometry(svgGeometry), partialGcpTransformOptions); return geometryToGeojsonGeometry(transformedGeometry); } /** * Transform an SVG string to geo space to a GeoJSON FeatureCollection * * This is a shortcut method, available as static method in order not to overpopulate intellisense suggestions * Note: since this converts to GeoJSON we assume geo-space is in lon-lat WGS84 and automatically set `destinationIsGeographic` to use geographically computed midpoints. * Note: Multi-geometries are not supported * * @param transformer - A GCP Transformer defining the transformation * @param svg - An SVG string to transform * @param partialGcpTransformOptions - GCP Transform options * @returns Input SVG string transformed to geo space, as a GeoJSON FeatureCollection */ static transformSvgStringToGeojsonFeatureCollection(transformer, svg, partialGcpTransformOptions) { const geojsonGeometries = []; for (const svgGeometry of stringToSvgGeometriesGenerator(svg)) { const geojsonGeometry = this.transformSvgToGeojson(transformer, svgGeometry, partialGcpTransformOptions); geojsonGeometries.push(geojsonGeometry); } return geojsonGeometriesToGeojsonFeatureCollection(geojsonGeometries); } /** * Transform a GeoJSON Geometry to resource space to a SVG geometry * * This is a shortcut method, available as static method in order not to overpopulate intellisense suggestions * Note: since this converts from GeoJSON we assume geo-space is in lon-lat WGS84 and automatically set `destinationIsGeographic` to use geographically computed midpoints. * Note: Multi-geometries are not supported * * @param transformer - A GCP Transformer defining the transformation * @param geojsonGeometry - GeoJSON Geometry to transform * @param partialGcpTransformOptions - GCP Transform options * @returns Input GeoJSON Geometry transform to resource space, as SVG geometry */ static transformGeojsonToSvg(transformer, geojsonGeometry, partialGcpTransformOptions) { partialGcpTransformOptions = mergePartialOptions({ geoIsGeographic: true }, partialGcpTransformOptions); // This middle step is needed to make typescript happy const transformedGeometry = transformer.transformToResource(geojsonGeometryToGeometry(geojsonGeometry), partialGcpTransformOptions); return geometryToSvgGeometry(transformedGeometry); } /** * Transform a GeoJSON FeatureCollection to resource space to a SVG string * * This is a shortcut method, available as static method in order not to overpopulate intellisense suggestions * Note: since this converts from GeoJSON we assume geo-space is in lon-lat WGS84 and automatically set `destinationIsGeographic` to use geographically computed midpoints. * Note: Multi-geometries are not supported * * @param transformer - A GCP Transformer defining the transformation * @param geojson - GeoJSON FeatureCollection to transform * @param partialGcpTransformOptions - GCP Transform options * @returns Input GeoJSON FeaturesCollection transformed to resource space, as SVG string */ static transformGeojsonFeatureCollectionToSvgString(transformer, geojson, partialGcpTransformOptions) { const svgGeometries = []; for (const geojsonGeometry of geojsonFeatureCollectionToGeojsonGeometries(geojson)) { const svgGeometry = this.transformGeojsonToSvg(transformer, geojsonGeometry, partialGcpTransformOptions); svgGeometries.push(svgGeometry); } return svgGeometriesToSvgString(svgGeometries); } /** * Create a Projected GCP Transformer from a Georeferenced Map * * @param georeferencedMap - A Georeferenced Map * @param options - Options, including GCP Transformer Options, and a transformation type to overrule the type defined in the Georeferenced Map * @returns A Projected GCP Transformer */ static fromGeoreferencedMap(georeferencedMap, options) { return new GcpTransformer(georeferencedMap.gcps, options?.transformationType || georeferencedMap.transformation?.type, options); } }