coordinateconversion

This bundle provides a tool that allows converting coordinates on the fly as well as searching for coordinates and picking them from the map.

Usage

To make the coordinate conversion available to the user, add the tool ID coordinateconversionToggleTool to a tool set.

The widget is registered at the component system with the widget role coordinateconversion.

The bundles provides an API documentation.

Configuration reference

The following code sample shows the configurable properties and its default values:

"coordinateconversion": {
    "Config": {
        "defaultFormat": "wgs84-lat-lon",
        "availableFormats": [
            "wgs84-decimal-degrees",
            "wgs84-degrees-decimal-minutes",
            "wgs84-lat-lon",
            "wgs84-pseudo-mercator",
            "utm-32"
        ],
        "locationSymbol": {
            "type": "picture-marker",
            "url": "resource('images/search-symbol-32.png')",
            "height": 24,
            "width": 24,
            "xoffset": 0,
            "yoffset": 0
        },
        "hideConversions": false
    }
}
Property Type Description
defaultFormat String ID of the format initially selected. A list of possible values is shown in the following table.
availableFormats Array of Strings A list of format IDs that can be selected for coordinate transformation (use IDs from the following table). If the list of available formats is empty, all formats listed in the following table are available.
locationSymbol SimpleMarkerSymbol or PictureMarkerSymbol The property locationSymbol allows to change the default symbol used to visualize the current location. See CoordinateConversion for details.
hideConversions Boolean Disables the part of the user interface that allows the user to activate additional conversions. Only the input form remains visible.

Well-known conversion formats

Name ID Description
WGS 84 (Decimal Degrees) wgs84-decimal-degrees WGS84-based coordinates in decimal degrees format
WGS 84 (Degrees Minutes) wgs84-degrees-decimal-minutes WGS84-based coordinates in degrees, decimal minutes format
WGS 84 (Degrees Minutes Seconds) wgs84-lat-lon WGS84-based coordinates in degrees, minutes and seconds format
WGS 84 (Web Mercator) wgs84-pseudo-mercator WGS84 with web mercator (pseudo mercator) base coordinates
ETRS89 / UTM zone 32N utm-32 Universal Transverse Mercator zone 32N
ETRS89 / UTM zone 33N utm-33 Universal Transverse Mercator zone 33N
ETRS89/DREF91/2016 / UTM zone 32N utm-32-2016 Universal Transverse Mercator zone 32N using german national realization of ETRS89 (DHHN2016)
ETRS89/DREF91/2016 / UTM zone 32N utm-33-2016 Universal Transverse Mercator zone 33N using german national realization of ETRS89 (DHHN2016)
DHDN Gauss-Krueger gauss-krueger The German Gauss Krueger format. The format switches between the different strips 2, 3, 4, and 5 automatically.
DHDN Gauss Krueger (Zone) gauss-krueger-[2|3|4|5] The German Gauss Krueger format for a specific zone (such as gauss-krueger-2).
LUREF / Luxembourg TM luref Luxembourg official reference system
SwissGrid LV95 swissgrid-lv95 Official reference system of Switzerland based on reference frame LV95 system
XY xy Longitude, latitude (WGS84)
MGRS mgrs Military Grid Reference System
UTM utm Universal Transverse Mercator
DD dd Decimal Degrees
DDM ddm Degrees decimal minutes
DMS dms Degrees minutes seconds

Use Cases

Changing the label of a format

The label of a format can be changed by overriding the I18N keys used by this bundle. To change the label of a format with a certain id, override the I18N key at coordinateconversion.formats[id].

For example:

// <app>/nls/bundle.js
export default {
    root: {
        coordinateconversion: {
            formats: {
                "wgs84-lat-lon": "WGS 84 DMS [CUSTOM]",
                "dd": "DD Format [CUSTOM]"
            }
        }
    },
    de: true
};

NOTE: Overriding I18N keys requires updating the app.json as well. You may also need to configure strings for additional languages used by your app. More details are available in the map.apps documentation.

Change the precision of the formats

You can adjust the precision of the formats provided via this bundle via the properties of the CustomConversionsRegistration service. The example JSON below show the service configuration with the default values:

"coordinateconversion": {
    "CustomConversionsRegistration": {
        "decimalPlaces": 2,
        "ddDecimalPlaces": 5,
        "dsDecimalPlaces": 3
    },
    "Config": {
        ...
    }
}

The following table shows the effect of changing these configuration properties on the individual formats.

Format ID Property Description
wgs84-lat-lon dsDecimalPlaces Sets the number of decimal places for seconds in the format. Example: 51° 57' 44.<999>"N.
wgs84-decimal-degrees ddDecimalPlaces Sets the number of decimal places for degrees in the format. Example: 51.<999>°N.
wgs84-degrees-decimal-minutes ddDecimalPlaces Sets the number of decimal places for decimal minutes. Example: 51° 57.<999>'N.
wgs84-pseudo-mercator decimalPlaces Sets the number of decimal places for numeric values. Example: 848,885.<99>.
utm-32 decimalPlaces Sets the number of decimal places for numeric values. Example: 405,574.<99>.
utm-33 decimalPlaces Sets the number of decimal places for numeric values. Example: -6,335.<99>.
gauss-krueger decimalPlaces Sets the number of decimal places for numeric values. Example: 2,611,778.<99>.
gauss-krueger-[2|3|4|5] decimalPlaces Sets the number of decimal places for numeric values. Example: 2,611,778.<99>.
luref decimalPlaces Sets the number of decimal places for numeric values. Example: 77,405.<99>.
swissgrid-lv95 decimalPlaces Sets the number of decimal places for numeric values. Example: 2,600,572.<99>.

Accessing the state of the widget

This bundle exposes some parts of the widget's state via an API. To access the widget's state, inject the service "coordinateconversion.Model":

{
    // ...
    "components": [
        {
            "name": "YourService",
            "references": [
                {
                    "name": "coordinatesModel",
                    "providing": "coordinateconversion.Model"
                }
            ]
        }
    ]
}

The model exposes the current marker location as a reactive property:

import { watchValue } from "apprt-core/reactivity";

const model = this.coordinatesModel!; // injected

// Access current value
// model.currentLocation is the current position of the marker (includes movements with the mouse)
// model.confirmedLocation only includes "fixed" markers (via form input or click in the map).
console.log(model.currentLocation);

// Watch value updates using the reactivity API.
this.cleanupWatch = watchValue(
    () => model.currentLocation,
    (location) => {
        if (location) {
            console.info(`Current location is ${location.x} ${location.y}`);
        }
    }
);

How to register custom formats

Adding a new coordinate format requires an implementation of the SegmentedFormat interface, registered using the interface name "coordinateconversion.SegmentedFormat".

The SegmentedFormat interface allows you to configure the new format's id, label and segments:

Additionally, a SegmentedFormat must provide methods to transform between various representations of coordinates on a map:

Point (ESRI Geometry) <---> Segments values (multiple Strings) <---> String

The following example implements a simple XY format with two input fields. To keep it simple, the example does not make use of I18N or advanced validation.

export class CustomXYFormat implements SegmentedFormat {
    readonly id = "xy-custom";
    readonly label = "XY (Custom)";
    readonly segments: CoordinateSegment[] = [
        {
            id: "x",
            label: "Longitude",
            example: "7.62",
            suffix: "° E",
            validate: validateNumber
        },
        {
            id: "y",
            label: "Latitude",
            example: "51.97",
            suffix: "° N",
            validate: validateNumber
        }
    ];

    readonly spatialReference = SpatialReference.WGS84;

    async pointToSegments(point: Point): Promise<Record<string, string>> {
        const amountOfDecimals = 4;
        const x = point.x.toFixed(amountOfDecimals);
        const y = point.y.toFixed(amountOfDecimals);
        return { x, y };
    }

    segmentsToString({ x, y }: Record<string, string>): string {
        return `${x}, ${y}`;
    }

    stringToSegments(input: string): ParseResult<Record<string, string>> {
        const match = input.match(/^([^,]+),(.*)$/);
        if (!match) {
            return {
                kind: "error",
                message: "Expected format: x, y"
            };
        }

        const x = (match[1] || "").trim();
        const y = (match[2] || "").trim();
        return {
            kind: "success",
            value: { x, y }
        };
    }

    async segmentsToPoint(segments: Record<string, string>): Promise<ParseResult<Point>> {
        return {
            kind: "success",
            value: new Point({
                x: parseFloat(segments["x"]!),
                y: parseFloat(segments["y"]!),
                spatialReference: { wkid: 4326 }
            })
        };
    }
}

const numberPattern = /^-?\d+[.]?\d*$/;

function validateNumber(input: string): boolean {
    return numberPattern.test(input);
}

How to register custom ESRI formats (LEGACY)

NOTE: This is the old way to register custom formats. It still works, but formats using this API will not be able to take full advantage of the widget's capabilities. For example, there will only be a single input text field for coordinates.

To register custom formats for coordinate conversion, you need to implement the interface @arcgis/core/widgets/CoordinateConversion/support/Format from the ArcGIS Maps SDK for JavaScript.

Note: This interface might be changed by Esri in future versions of the API. Therefore, we cannot guarantee, that it is stable and backward compatible.

The following code sample shows how to add a custom lat/lon format:

import Format from "@arcgis/core/widgets/CoordinateConversion/support/Format";
import Point from "@arcgis/core/geometry/Point";
import { webMercatorToGeographic } from "@arcgis/core/geometry/support/webMercatorUtils";

export class XYFormatFactory {
    /**
     * @see https://developers.arcgis.com/javascript/latest/sample-code/widgets-coordinateconversion-custom/index.html
     */
    createInstance(): Format {
        // Regular expression to find a number
        const numberSearchPattern = /-?\d+[\\.]?\d*/;

        const formatProperties = {
            // used to identify the format. This key is used in the defaultFormat option of the widget.
            // it is only required if the name is localized and has no stable value.
            id: "xy-custom",
            // The format's name should be unique with respect to other formats used by the widget
            name: "XY (Custom)",
            conversionInfo: {
                // Define a convert function
                // Point -> Position
                convert(point: Point) {
                    const returnPoint = point.spatialReference.isWGS84 ? point : (webMercatorToGeographic(point) as Point);
                    const x = returnPoint.x.toFixed(4);
                    const y = returnPoint.y.toFixed(4);
                    return {
                        location: returnPoint,
                        coordinate: `${x}, ${y}`
                    };
                },
                // Define a reverse convert function
                // String -> Point
                reverseConvert(coordinate: string) {
                    try {
                        const parts = coordinate.split(",");
                        return new Point({
                            x: parseFloat(parts[0]!),
                            y: parseFloat(parts[1]!),
                            spatialReference: { wkid: 4326 }
                        });
                    } catch (e) {
                        // Esri widget to display error message
                        console.error(e);
                        throw e;
                    }
                }
            },
            // Define each segment of the coordinate
            coordinateSegments: [
                {
                    alias: "X",
                    description: "Longitude",
                    searchPattern: numberSearchPattern
                },
                {
                    alias: "Y",
                    description: "Latitude",
                    searchPattern: numberSearchPattern
                }
            ],
            defaultPattern: "X\u00B0, Y\u00B0"
        };

        return new Format(formatProperties);
    }
}

Finally, register the format component as service coordinateconversion.Format in a manifest.json file as in the following code sample:

{
    "name": "XYFormatFactory",
    "provides": ["coordinateconversion.Format"],
    "instanceFactory": true
}