@allmaps/attach
Attaching maps together:
- Link resource points on different maps using ‘attachments’.
- 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.
- Solve the attached transformation and infer new GCPs from the attachments that bring the maps closer together.
How it works
Section titled “How it works”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
differentHandednessfor 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.
Installation
Section titled “Installation”This is an ESM-only module that works in browsers and in Node.js.
Install with npm:
npm install @allmaps/attachQuickstart
Section titled “Quickstart”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 annotationconst annotation = await fetch(annoationUrl).then((response) => response.json())
// Create georeferencedMaps from the annotationconst georeferencedMaps = parseAnnotation(annotation)const georeferencedMap0 = georeferencedMaps[0]const georeferencedMap1 = georeferencedMaps[1]
georeferencedMap0.gcps.length // return 3: map with 3 GCPsgeoreferencedMap1.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 map1const 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 RCPsconst attachedtransformation = AttachedTransformation.fromGeoreferencedMaps( [ georeferencedMap0, georeferencedMap1 ], rcps)
// Use the .toGeoreferencedMaps() method to solve the attached transformation and infer new GCPs from the attachmentsconst 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 GCPsresultingGeoreferencedMap1.gcps.length // return 4: map with 3 + 1 = 4 GCPsresultingGeoreferencedMap0.gcps[3] // { resource: [4779, 261] , geo: [4.941781094220815, 52.34760910486503] }resultingGeoreferencedMap1.gcps[3] // { resource: [414, 3597] , geo: [4.941781094220815, 52.34760910486503] }Using AttachedTransformation directly
Section titled “Using AttachedTransformation directly”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.
Attached transformation options
Section titled “Attached transformation options”The following options are available for Attached transformations:
| Option | Description | Type | Default |
|---|---|---|---|
averageOut | Average 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. | boolean | true |
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:
| Option | Description | Type | Default |
|---|---|---|---|
transformationType | TransformationType to use when building the attached transformation coefficient matrix. This overrules the map’s TransformationType, unless useMapTransformationTypes is true. | TransformationType | 'polynomial' |
useMapTransformationTypes | Let transformationType overrule the map’s TransformationType. | boolean | false |
clone | Deep Clone the map and its transformer and transformations before returning the results. This prevents from overriding object properties like GCPs on the input objects. | boolean | true |
evaluateAttachmentScps | For both Source Control Points of an attachment, evaluate them using the solved attached transformation and create a GCP on the corresponding map. | boolean | true |
evaluateSingleScps | For Source Control Points without a matching pair, evaluate them using the solved attached transformation and create a GCP on the corresponding map. | boolean | false |
evaluateGcps | For existing GCPs, re-evaluate them using the solved attached transformation. | boolean | false |
removeExistingGcps | Remove existing GCPs. | boolean | false |
Typing
Section titled “Typing”RCPs are defined as follows:
export type Rcp = { type: 'rcp' id: string mapId: string resource: Point}License
Section titled “License”MIT
new AttachedTransformation(transformationsById, attachments, options)
Section titled “new AttachedTransformation(transformationsById, attachments, options)”Create a Attached Transformation
Parameters
Section titled “Parameters”transformationsById(Map<string, BaseIndependentLinearWeightsTransformation>)attachments(Array<Attachment>)options?(Partial<AttachedTransformationOptions> | undefined)
Returns
Section titled “Returns”AttachedTransformation.
AttachedTransformation#ScpsById
Section titled “AttachedTransformation#ScpsById”Array<Array<Scp>>AttachedTransformation#attachments
Section titled “AttachedTransformation#attachments”Array<Attachment>AttachedTransformation#coefsArrayMatrices
Section titled “AttachedTransformation#coefsArrayMatrices”[Array<Array<number>>, Array<Array<number>>]AttachedTransformation#coefsArrayMatricesSize
Section titled “AttachedTransformation#coefsArrayMatricesSize”[[number, number], [number, number]]AttachedTransformation#coefsArrayMatrix
Section titled “AttachedTransformation#coefsArrayMatrix”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)”Parameters
Section titled “Parameters”newSourcePoint([number, number])id(string)
Returns
Section titled “Returns”[number, number].
AttachedTransformation#getCoefsArrayMatrices()
Section titled “AttachedTransformation#getCoefsArrayMatrices()”Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”[Array<Array<number>>, Array<Array<number>>].
AttachedTransformation#getCoefsArrayMatrix()
Section titled “AttachedTransformation#getCoefsArrayMatrix()”Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”Array<Array<number>>.
AttachedTransformation#getDestinationPointsArrays()
Section titled “AttachedTransformation#getDestinationPointsArrays()”Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”[Array<number>, Array<number>].
AttachedTransformation#options?
Section titled “AttachedTransformation#options?”TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; } & { ...; }AttachedTransformation#processWeightsArrays()
Section titled “AttachedTransformation#processWeightsArrays()”Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”void.
AttachedTransformation#solve()
Section titled “AttachedTransformation#solve()”Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”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.
Parameters
Section titled “Parameters”There are no parameters.
Returns
Section titled “Returns”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>AttachedTransformation#weightsArrays?
Section titled “AttachedTransformation#weightsArrays?”[Array<number>, Array<number>]AttachedTransformation#weightsArraysById?
Section titled “AttachedTransformation#weightsArraysById?”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.
Parameters
Section titled “Parameters”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
Returns
Section titled “Returns”AttachedTransformation.
AttachedTransformationFromGeoreferencedMapOptions
Section titled “AttachedTransformationFromGeoreferencedMapOptions”TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; }AttachedTransformationOptions
Section titled “AttachedTransformationOptions”TransformationTypeInputs & { georeferencedMapsById?: Map<string, GeoreferencedMap>; projectedGcpTransformersById?: Map<string, ProjectedGcpTransformer>; ... 7 more ...; removeExistingGcps: boolean; } & { ...; }Attachment
Section titled “Attachment”[Scp, Scp]Fields
Section titled “Fields”id(string)mapId(string)resource([number, number])type('rcp')
RcpsInput
Section titled “RcpsInput”Fields
Section titled “Fields”rcps(Array<Rcp>)
Fields
Section titled “Fields”destination?([number, number])id(string)source([number, number])transformationId(string)
Fields
Section titled “Fields”destination?([number, number])source([number, number])transformationId(string)