Skip to content

@allmaps/attach

Attaching maps together:

  1. Link resource points on different maps using ‘attachments’.
  2. Express this attaching using an attached transformation that considers all relevant map transformations jointly and minimizes the distance in geo space of the attachments’ transformed resource points.
  3. Solve the attached transformation and infer new GCPs from the attachments that bring the maps closer together.

The AttachedTransformation class from this package allows to work with ‘attached transformations’: multiple transformation that are coupled by ‘attachments’ and can hence be solved jointly.

Just like the various transformation classes from @allmaps/transform, an attached transformation is expressed mathematically using a coefficient matrix. Solving this matrix allows us to obtain the weights that define the transformation, which in turn allows us to evaluate the transformation at new source points. For attached transformations, the coefficient matrix is a block matrix, built from the constituent transformations’ coefficient matrices, with added rows that express how each attachment links two transformations by demanding that their corresponding destination points should (ideally) be equal. More details about the mathematical approach can be found here.

To make it more practical to create and solve attached transformation when starting from Annotations or Georeferenced Maps, static functions are available to input and output to Georeferenced Maps. They read in Georeferenced Maps and a list of ‘Resource Control Points’ (RCPs). These are points that are defined on a map’s resource, but don’t (yet) have geo coordinates. Using their id, they can be linked to other RCPs, where two RCPs with matching id’s will create an ‘attachment’ between the corresponding maps. When solving the attached transformation and evaluating the RCPs using the new found weights corresponding to the transformation of their corresponding map, we can compute their geo coordinates and hence infer new Ground Control Points. These new GCPs

Some details:

  • An attachment is defined by two ‘Source Control Points’ (SCPs). These are the RCPs, where the ‘resource’ coordinate has been translated to ‘source’, as custom in a transformation (taking into account differentHandedness for example).
  • When there are more then two RCPs with a matching id, an attachment is created between the first and second, first and third, first and fourth etc.

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

Install with npm:

Terminal window
npm install @allmaps/attach

When starting from an Annotation or Georeferenced Map, the fastest way to deal with multiple maps that are ‘attached together’ is:

import { parseAnnotation } from '@allmaps/annotation'
import { AttachedTransformation } from '@allmaps/attach'
// Fetch an annotation
const annotation = await fetch(annoationUrl).then((response) => response.json())
// Create georeferencedMaps from the annotation
const georeferencedMaps = parseAnnotation(annotation)
const georeferencedMap0 = georeferencedMaps[0]
const georeferencedMap1 = georeferencedMaps[1]
georeferencedMap0.gcps.length // return 3: map with 3 GCPs
georeferencedMap1.gcps.length // return 3: map with 3 GCPs
// Define Resource Control Points
// Here two RCPs are have a matching 'id' and will create one attachment between mapO and map1
const rcps = [
{
type: 'rcp',
id: 'center',
mapId: georeferencedMap0.id,
resource: [4779, 261] // resource point on map0
},
{
type: 'rcp',
id: 'center', // same id
mapId: georeferencedMap1.id, // different mapId
resource: [414, 3597] // resource point on map1
}
]
// Create a AttachedTransformation instance from the Georeferenced Maps and the RCPs
const attachedtransformation = AttachedTransformation.fromGeoreferencedMaps(
[
georeferencedMap0,
georeferencedMap1
],
rcps
)
// Use the .toGeoreferencedMaps() method to solve the attached transformation and infer new GCPs from the attachments
const resultingGeoreferencedMaps = attachedtransformation.toGeoreferencedMaps()
const resultingGeoreferencedMap0 = resultingGeoreferencedMaps[0]
const resultingGeoreferencedMap1 = resultingGeoreferencedMaps[1]
// Here one attachment created one new GCP on both of it's maps,
// with the original resource point and a new, identical geo point.
resultingGeoreferencedMap0.gcps.length // return 4: map with 3 + 1 = 4 GCPs
resultingGeoreferencedMap1.gcps.length // return 4: map with 3 + 1 = 4 GCPs
resultingGeoreferencedMap0.gcps[3] // { resource: [4779, 261] , geo: [4.941781094220815, 52.34760910486503] }
resultingGeoreferencedMap1.gcps[3] // { resource: [414, 3597] , geo: [4.941781094220815, 52.34760910486503] }

It is also possible to use the AttachedTransformation class directly. It has similar methods then the various Transformation classes from @allmaps/transform to e.g. create a coefficient matrix, solve it and evaluate it.

The following options are available for Attached transformations:

OptionDescriptionTypeDefault
averageOutAverage out the resulting geo coordinates for each id. For inexact transformations (like 'polynomial') the geo coordinates will in general not be equal. This forces them be equal. For exact transformation types (like 'thinPlateSpline') the geo coordinates will be (quasi) identical making this averaging not (strictly) necessary. Note: the averaging happens in projected geo coordinates.booleantrue

Attached transformation From Georeferenced Map options

Section titled “Attached transformation From Georeferenced Map options”

The following options are available for Attached transformations from Georeferenced Maps, in addition to the Attached transformation options:

OptionDescriptionTypeDefault
transformationTypeTransformationType to use when building the attached transformation coefficient matrix. This overrules the map’s TransformationType, unless useMapTransformationTypes is true.TransformationType'polynomial'
useMapTransformationTypesLet transformationType overrule the map’s TransformationType.booleanfalse
cloneDeep Clone the map and its transformer and transformations before returning the results. This prevents from overriding object properties like GCPs on the input objects.booleantrue
evaluateAttachmentScpsFor both Source Control Points of an attachment, evaluate them using the solved attached transformation and create a GCP on the corresponding map.booleantrue
evaluateSingleScpsFor Source Control Points without a matching pair, evaluate them using the solved attached transformation and create a GCP on the corresponding map.booleanfalse
evaluateGcpsFor existing GCPs, re-evaluate them using the solved attached transformation.booleanfalse
removeExistingGcpsRemove existing GCPs.booleanfalse

RCPs are defined as follows:

export type Rcp = {
type: 'rcp'
id: string
mapId: string
resource: Point
}

MIT

new AttachedTransformation(transformationsById, attachments, options)

Section titled “new AttachedTransformation(transformationsById, attachments, options)”

Create a Attached Transformation

  • transformationsById (Map<string, BaseIndependentLinearWeightsTransformation>)
  • attachments (Array<Attachment>)
  • options? (Partial<AttachedTransformationOptions> | undefined)

AttachedTransformation.

Array<Array<Scp>>
Array<Attachment>
[Array<Array<number>>, Array<Array<number>>]

AttachedTransformation#coefsArrayMatricesSize

Section titled “AttachedTransformation#coefsArrayMatricesSize”
[[number, number], [number, number]]
Array<Array<number>>

AttachedTransformation#coefsArrayMatrixSize

Section titled “AttachedTransformation#coefsArrayMatrixSize”
[number, number]

AttachedTransformation#destinationPointsArrays

Section titled “AttachedTransformation#destinationPointsArrays”
[Array<number>, Array<number>]

AttachedTransformation#evaluateFunction(newSourcePoint, id)

Section titled “AttachedTransformation#evaluateFunction(newSourcePoint, id)”
  • newSourcePoint ([number, number])
  • id (string)

[number, number].

AttachedTransformation#getCoefsArrayMatrices()

Section titled “AttachedTransformation#getCoefsArrayMatrices()”

There are no parameters.

[Array<Array<number>>, Array<Array<number>>].

AttachedTransformation#getCoefsArrayMatrix()

Section titled “AttachedTransformation#getCoefsArrayMatrix()”

There are no parameters.

Array<Array<number>>.

AttachedTransformation#getDestinationPointsArrays()

Section titled “AttachedTransformation#getDestinationPointsArrays()”

There are no parameters.

[Array<number>, Array<number>].

TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; } & { ...; }

AttachedTransformation#processWeightsArrays()

Section titled “AttachedTransformation#processWeightsArrays()”

There are no parameters.

void.

There are no parameters.

void.

AttachedTransformation#toGeoreferencedMaps()

Section titled “AttachedTransformation#toGeoreferencedMaps()”

Create Georeferenced Maps from this Attached Transformation. This will solve the Attached Transformation, evaluate all attachements (in all source control points), infer GCPs from them, and add them to the original Georeferenced Maps.

This only works if this AttachedTransformation has been created from Georeferenced Maps.

There are no parameters.

Array<{ type: "GeoreferencedMap"; resource: { type: "ImageService1" | "ImageService2" | "ImageService3" | "Canvas"; id: string; height?: number | undefined; width?: number | undefined; partOf?: ({ type: string; id: string; label?: Record<string, (string | number | boolean)[]> | undefined; } & { partOf?: ({ type: str....

AttachedTransformation#trailingCumulativeCoefsArrayMatrixSizeById

Section titled “AttachedTransformation#trailingCumulativeCoefsArrayMatrixSizeById”
Map<string, [number, number]>

AttachedTransformation#transformationsById

Section titled “AttachedTransformation#transformationsById”
Map<string, BaseIndependentLinearWeightsTransformation>
[Array<number>, Array<number>]
Map<string, [Array<number>, Array<number>]>

AttachedTransformation.fromGeoreferencedMaps(georeferencedMaps, rcps, options)

Section titled “AttachedTransformation.fromGeoreferencedMaps(georeferencedMaps, rcps, options)”

Create a AttachedTransformation from Georeferenced Maps

By default, a Projected GCP Transformer is created for each Georeferenced Map, and from it a Thin Plate Spline transformation is created and passed to the AttachedTransformation.

Use the options to specify another transformation type for all maps, or specifically set the option ‘useMapTransformationTypes’ to true to use the type defined in the Georeferenced Map.

  • georeferencedMaps (Array<{ type: "GeoreferencedMap"; resource: { type: "ImageService1" | "ImageService2" | "ImageService3" | "Canvas"; id: string; height?: number | undefined; width?: number | undefined; partOf?: ({ type: string; id: string; label?: Record<string, (string | number | boolean)[]> | undefined; } & { partOf?: ({ type: str...)
    • Georeferenced Maps
  • rcps (Array<Rcp>)
  • options? (Partial<{ internalProjection: Projection; projection: Projection; } & { differentHandedness: boolean; } & { maxDepth: number; minOffsetRatio: number; ... 6 more ...; preToResource: ProjectionFunction; } & MultiGeometryOptions & TransformationTypeInputs & { ...; }> | undefined)
    • Options, including Projected GCP Transformer Options, and a transformation type to overrule the type defined in the Georeferenced Map

AttachedTransformation.

AttachedTransformationFromGeoreferencedMapOptions

Section titled “AttachedTransformationFromGeoreferencedMapOptions”
TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; }
TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; } & { ...; }
[Scp, Scp]
  • id (string)
  • mapId (string)
  • resource ([number, number])
  • type ('rcp')
  • rcps (Array<Rcp>)
  • destination? ([number, number])
  • id (string)
  • source ([number, number])
  • transformationId (string)
  • destination? ([number, number])
  • source ([number, number])
  • transformationId (string)