New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@allmaps/transform

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@allmaps/transform

Coordinate transformation functions

  • 1.0.0-beta.30
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
201
increased by183.1%
Maintainers
1
Weekly downloads
 
Created
Source

@allmaps/transform

This module serves to transform points, lines, polygons and other spatial features from a cartesian (x, y) source plane to a destination plane. It does this using a set of control points, who's coordinates are known in both planes, and a specific transformation algorithm.

It is used in @allmaps/render and @allmaps/tileserver, two packages where we produce a georeferenced image by triangulating a IIIF image and drawing these triangles on a map in a specific new location, with the triangle's new vertex location computed by the transformer of this package. The transformer is constructed from control points in the annotation and transforms points from the resource coordinate space of a IIIF Resource to the geo coordinate space of an interactive map.

Care was taken to make this module usable and useful outside of the Allmaps context as well! Feel free to incorporate it in your project.

How it works

This package exports the GcpTransformer class. Its instances (called transformers) are built from a set of Ground Control Points (GCPs) and a specified transformation type. Using these, a forward and backward transformation can be built that maps arbitrary points in one plane to the corresponding points in the other plane. The transformer has dedicated functions that use this transformation to transform points and more complex geometries like line strings and polygons.

Transform vs. GDAL

The transformation algorithms of this package correspond to those of GDAL and the results are (nearly) identical. See the tests for details.

For a little history: this library started out as a JavaScript port of gdaltransform (as described in this notebook) and initially only implemented polynomial transformations of order 1. Later Thin Plate Spline transformations were added (see this notebook) amongst other transformations, which lead to a refactoring using the ml-matrix library. This library is used for creating and solving the linear systems of equations that are at the heart of each of each of these transformations.

Defining Ground Control Points

GCPs can be supplied as an array of objects containing source and destination coordinates:

type TransformGcp = {
  source: [number, number]
  destination: [number, number]
}

Or you can supply an array of objects containing resource and geo coordinates. This is the format used in Georeference Annotations:

type Gcp = {
  resource: [number, number],
  geo: [number, number]
}

Supported transformation types

TypeOptionsDescriptionPropertiesMinimum number of GCPs
helmertHelmert transformation or 'similarity transformation'Preserves shape and angle2
polynomial (default)order: 1First order polynomial transformationPreserves lines and parallelism3
polynomialorder: 2Second order polynomial transformationSome bending flexibility6
polynomialorder: 3Third order polynomial transformationMore bending flexibility10
thinPlateSplineThin Plate Spline transformation or 'rubber sheeting' (with affine part)Exact, smooth (see this notebook)3
projectiveProjective or 'perspective' transformation, used for aerial imagesPreserves lines and cross-ratios4

Transformation methods

A transformer is build from a set of GCPs and a transformation type. It contains the forward and backward transformation, and has specific methods to apply it to transform geometries forward and backward.

All transformer methods accepts points, line strings as well as polygons, both as simple geometries or GeoJSON geometries. There are, however, separate methods for transforming to simple geometries or to GeoJSON geometries. There are also separate methods for transforming forward or backward.

Hence, the main methods are: transformForward(), transformForwardAsGeojson(), transformBackward() and transformBackwardAsGeojson()

Alternatively the same four methods are available with more expressive term for the Allmaps use case: replacing Forward by ToGeo and Backward by ToResource. E.g.: transformToGeoAsGeojson().

The simple geometries are:

type Point = [number, number]

type LineString = Point[]

type Polygon = Point[][]
// A polygon is an array of rings of at least three points
// Rings are not closed: the first point is not repeated at the end.
// There is no requirement on winding order.

type Geometry = Point | LineString | Polygon

Refined transfromation of LineStrings and Polygons

When transforming a line or polygon, it can happen that simply transforming every point is not sufficient. Two factors are at play which may require a more granular transformation: the transformation (which can be non-shape preserving, as is the case with all transformation in this package except for Helmert and 1st degree polynomial) or the geographic nature of the coordinates (where lines are generally meant as 'great arcs' but could be interpreted as lon-lat cartesian lines). An algorithm will therefore recursively add midpoints in each segment (i.e. between two points) to make the line more granular. A midpoint is added at the transformed middle point of the original segment on the condition that the ratio of (the distance between the middle point of the transformed segment and the transformed middle point of the original segment) to the length of the transformed segment, is larger then a given ratio. The following options specify if and with what degree of detail such extra points should be added.

OptionDescriptionDefault
maxOffsetRatioMaximum offset ratio (smaller means more midpoints)0
maxDepthMaximum recursion depth (higher means more midpoints)0
sourceIsGeographicUse geographic distances and midpoints for lon-lat source pointsfalse (true when source is GeoJSON)
destinationIsGeographicUse geographic distances and midpoints for lon-lat destination pointsfalse (true when destination is GeoJSON)

Installation

This is an ESM-only module that works in browsers and in Node.js.

Install with npm:

npm install @allmaps/transform

Usage

Point

import { GcpTransformer } from '@allmaps/transform'

const transformGcps3 = [
  {
    source: [518, 991],
    destination: [4.9516614, 52.4633102]
  },
  {
    source: [4345, 2357],
    destination: [5.0480391, 52.5123762]
  },
  {
    source: [2647, 475],
    destination: [4.9702906, 52.5035815]
  }
]

const transformer = new GcpTransformer(transformGcps3, 'helmert')

const transformedPoint = transformer.transformForward([100, 100])
// transformedPoint = [4.9385700843392435, 52.46580484503631]

const transformedPoint = transformer.transformBackward([
  4.9385700843392435, 52.46580484503631
])
// transformedPoint = [100, 100]

LineString

In this example we transform backward, and from a GeoJSON Geometry.

export const transformGcps7 = [
  {
    source: [0, 0],
    destination: [0, 0]
  },
  {
    source: [100, 0],
    destination: [20, 0]
  },
  {
    source: [200, 100],
    destination: [40, 20]
  },
  {
    source: [200, 200],
    destination: [40, 40]
  },
  {
    source: [150, 250],
    destination: [40, 100]
  },
  {
    source: [100, 200],
    destination: [20, 40]
  },
  {
    source: [0, 100],
    destination: [0, 20]
  }
]

const transformOptions = {
  maxOffsetRatio: 0.001,
  maxDepth: 2
}
// We transform backward (from destination to source) and have GeoJSON input.
// Hence `destinationIsGeographic: true` will be set automatically

const transformer = new GcpTransformer(transformGcps7, 'polynomial')

const lineStringGeoJSON = {
  type: 'LineString',
  coordinates: [
    [10, 50],
    [50, 50]
  ]
}

const transformedLineString = transformer.transformBackward(
  lineStringGeoJSON,
  transformOptions
)
// transformedLineString = [
//   [31.06060606060611, 155.30303030303048],
//   [80.91200458875993, 165.7903106766409],
//   [133.1658635549907, 174.5511756850417],
//   [185.89024742146262, 181.22828756380306],
//   [237.12121212121218, 185.60606060606085]
// ]

// Notice how the result has two layers of midpoints!
// In a first step the point [133.16, 174.55] is added between the start and end point
// Then [80.91, 165.79] and [185.89, 181.22] are added in between.

Polygon

In this example we transform to a GeoJSON Geometry.

export const transformGcps6 = [
  {
    source: [1344, 4098],
    destination: [4.4091165, 51.9017125]
  },
  {
    source: [4440, 3441],
    destination: [4.5029222, 51.9164451]
  },
  {
    source: [3549, 4403],
    destination: [4.4764224, 51.897309]
  },
  {
    source: [1794, 2130],
    destination: [4.4199066, 51.9391509]
  },
  {
    source: [3656, 2558],
    destination: [4.4775683, 51.9324358]
  },
  {
    source: [2656, 3558],
    destination: [4.4572643, 51.9143043]
  }
]

const transformOptions = {
  maxOffsetRatio: 0.00001,
  maxDepth: 1
}

const transformer = new GcpTransformer(transformGcps6, 'thinPlateSpline')

const polygon = [
  [
    [1000, 1000],
    [1000, 2000],
    [2000, 2000],
    [2000, 1000]
  ]
]

const transformedPolygonGeoJSON = transformer.transformForwardAsGeojson(
  polygon,
  transformOptions
)
// const transformedPolygonGeoJSON = {
//   type: 'Polygon',
//   coordinates: [
//     [
//       [4.388957777030093, 51.959084191571606],
//       [4.390889520773774, 51.94984430356657],
//       [4.392938913951547, 51.94062947962427],
//       [4.409493277493718, 51.94119110133424],
//       [4.425874493300959, 51.94172557475595],
//       [4.4230497784967655, 51.950815146974556],
//       [4.420666790347598, 51.959985351835975],
//       [4.404906205946158, 51.959549039424715],
//       [4.388957777030093, 51.959084191571606]
//     ]
//   ]
// }

API

Table of Contents

allmaps/transform

GcpTransformer

A Ground Control Point Transformer, containing a forward and backward transformation and specifying functions to transform geometries using these transformations.

Parameters
  • gcps (Array<TransformGcp> | Array<Gcp>) An array of Ground Control Points (GCPs)
  • type TransformationType The transformation type (optional, default 'polynomial')
transformForward

Transforms a Geometry or a GeoJSON geometry forward to a Geometry

Parameters
  • input (Geometry | GeojsonGeometry) Geometry or GeoJSON geometry to transform
  • options PartialTransformOptions? Transform options

Returns Geometry Forward transform of input as Geometry

transformForwardAsGeojson

Transforms a Geometry or a GeoJSON geometry forward to a GeoJSON geometry

Parameters
  • input (Geometry | GeojsonGeometry) Geometry or GeoJSON geometry to transform
  • options PartialTransformOptions? Transform options

Returns GeojsonGeometry Forward transform of input, as GeoJSON geometry

transformBackward

Transforms a geometry or a GeoJSON geometry backward to a Geometry

Parameters
  • input (Geometry | GeojsonGeometry) Geometry or GeoJSON geometry to transform
  • options PartialTransformOptions? Transform options

Returns Geometry backward transform of input, as geometry

transformBackwardAsGeojson

Transforms a Geometry or a GeoJSON geometry backward to a GeoJSON geometry

Parameters
  • input (Geometry | GeojsonGeometry) Geometry or GeoJSON geometry to transform
  • options PartialTransformOptions? Transform options

Returns GeojsonGeometry backward transform of input, as GeoJSON geometry

transformToGeo

Transforms Geometry or GeoJSON geometry forward, as Geometry

Parameters
  • input (Geometry | GeojsonGeometry) Input to transform
  • options

Returns Geometry Forward transform of input, as Geometry

transformToGeoAsGeojson

Transforms a Geometry or a GeoJSON geometry forward, to a GeoJSON geometry

Parameters
  • input (Geometry | GeojsonGeometry) Input to transform
  • options

Returns Geometry Forward transform of input, as GeoJSON geometry

transformToResource

Transforms a Geometry or a GeoJSON geometry backward, to a Geometry

Parameters
  • input (Geometry | GeojsonGeometry) Input to transform
  • options

Returns Geometry Backward transform of input, as a Geometry

transformToResourceAsGeojson

Transforms a Geometry or a GeoJSON geometry backward, to a GeoJSON geometry

Parameters
  • input (Geometry | GeojsonGeometry) Input to transform
  • options

Returns GeojsonGeometry Backward transform of input, as a GeoJSON geometry

transformSvgToGeojson

Transforms a SVG geometry forward to a GeoJSON geometry

Parameters
  • geometry SvgGeometry SVG geometry to transform
  • transformOptions

Returns GeojsonGeometry Forward transform of input, as a GeoJSON geometry

transformGeojsonToSvg

Transforms a GeoJSON geometry backward to a SVG geometry

Parameters
  • geometry GeojsonGeometry GeoJSON geometry to transform
  • transformOptions

Returns SvgGeometry Backward transform of input, as SVG geometry

Notes

  • Only linearly independent control points should be considered when checking if the criterion for the minimum number of control points is met. For example, three control points that are collinear (one the same line) only count as two linearly independent points. The current implementation doesn't check such linear (in)dependance, but building a transformer with insufficient linearly independent control points will result in a badly conditioned matrix (no error but diverging results) or non-invertible matrix (error when inverting matrix).
  • The transform functions are map-projection agnostic: they describe a transformation for one cartesian (x, y) plane to another. Using control points with (longitude, latitude) coordinates will produce a transformation from or to the cartesian plane of an equirectangular projection. (The only semi-exception to this is when using the destinationIsGeographic and sourceIsGeographic parameters - although these consider coordinates as lying on a sphere more than as projection coordinates.)

CLI

The @allmaps/cli package creates and interface for four specific use cases:

  • Transforming points to points.
  • Transforming SVG geometries from the resource coordinates space of a IIIF resource to GeoJSON objects in the geo coordinate space of an interactive map.
  • Transforming GeoJSON objects from the geo coordinate space of an interactive map to SVG objects in the resource coordinates space of a IIIF resource, given (the GCPs and transformation type from) a Georeference Annotation
  • Vice versa: transforming SVG objects from the resource coordinates to GeoJSON objects in the geo coordinate space.
  • Transforming the SVG resource mask included in a Georeference Annotation to a GeoJSON Polygon.

Benchmark

Here are some benchmarks on building and using a transformer, as computed on a 2023 MacBook Air M2.

Creating a transformer (with 10 points) (and transform 1 point)

TypeOptionsOps/s
helmert71338
polynomialorder: 1163419
polynomialorder: 286815
polynomialorder: 333662
thinPlateSpline27905
projective36202

Using a transformer (with 10 points) to transform 1 point

TypeOptionsOps/s
helmert27398212
polynomialorder: 122364872
polynomialorder: 219126410
polynomialorder: 33925102
thinPlateSpline484141
projective22657850

See ./bench/index.js.

The benchmark can be run with pnpm run bench.

Keywords

FAQs

Package last updated on 09 Feb 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc