Learn how to display geometries in different projections.
A geometry projection transforms the vertices of a geometric shape from one coordinate system (a spatial reference) to another. For example, you can project a geographic coordinate system such as WGS84 (4326) to a projected coordinate system such as World Sinusoidal (54008).
Each projection can maintain one of the following: area, angle, or direction. The projection you use is based on your application's requirements. For example, if you have data centered on the North Pole, the Web Mercator (3857) spatial reference is typically not used as the pole features are not correctly represented by the projection; there is a large area distortion. Instead, you might use the North Pole Gnomonic (102034) spatial reference because it preserves the area around the North Pole.
In this tutorial, you will project GeoJSON features using the projection engine and a spatial reference chosen from the selector. A center point and buffer graphic are also added to the map view.
Prerequisites
There are no prerequisites for this tutorial.
Steps
Create a new pen
- Go to CodePen to create a new pen for your mapping application.
Add HTML elements
-
In CodePen > HTML, add HTML and CSS to create a page with
view
andDiv wkid
elements. Theview
is the element displays the map and its CSS resets any browser settings so it can consume the full width and height of the browser. TheDiv wkid
options will beWeb Mercator
andWG
. CreateS84 <script
and> <link
tags to reference the CSS and JS library.> Use dark colors for code blocks <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" /> <title>Display projected geometries</title> <style> html, body, #viewDiv { padding: 0; margin: 0; height: 100%; width: 100%; background-color: #ffffff; } #wkid { position: absolute; top: 15px; right: 15px; width: 220px; font-family: "Avenir Next"; font-size: 1em; } </style> <link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/light/main.css" /> <script src="https://js.arcgis.com/4.30"></script> </head> <body> <div id="viewDiv"></div> <select id="wkid" class="esri-widget esri-select"> <option value="3857" disabled>Select a projection</option> <optgroup label="Equidistant (maintain length)"> <option value="4326" selected>WGS84 (GCS) -> pseudo Plate Carrée (Cylindrical)</option> </optgroup> <optgroup label="Compromise (distort all)"> <option value="3857">Web Mercator Auxiliary Sphere (Cylindrical)</option> </optgroup> </select> </body> </html>
Add modules
-
In the
require
statement, add the modules.The ArcGIS Maps SDK for JavaScript is available as AMD modules and ES modules, but this tutorial is based on AMD. The AMD
require
function uses references to determine which modules will be loaded – for example, you can specify"esri/
for loading the Map module. After the modules are loaded, they are passed as parameters (e.g.Map" Map
) to the callback function where they can be used in your application. It is important to keep the module references and callback parameters in the same order. To learn more about the API's different modules visit the Overview Guide page.Use dark colors for code blocks <script> require([ "esri/views/MapView", "esri/Map", "esri/request", "esri/geometry/SpatialReference", "esri/Graphic", "esri/geometry/Point", "esri/geometry/Polyline", "esri/layers/GeoJSONLayer", "esri/geometry/geometryEngine", "esri/geometry/projection", ], ( MapView, Map, esriRequest, SpatialReference, Graphic, Point, Polyline, GeoJSONLayer, geometryEngine, projection ) => { }); </script>
Set global variables and styles
Set the global variables needed to switch between spatial references and to recreate the view. The polygon and point styles will style the buffer created in a later step.
-
Create
spatial
andReference view
variables that will be used when switching between spatial references. Set thecoordinates
variable with thecoordinates
div
to display the projected x/y of the center point on the map.Use dark colors for code blocks "esri/views/MapView", "esri/Map", "esri/request", "esri/geometry/SpatialReference", "esri/Graphic", "esri/geometry/Point", "esri/geometry/Polyline", "esri/layers/GeoJSONLayer", "esri/geometry/geometryEngine", "esri/geometry/projection", ], ( MapView, Map, esriRequest, SpatialReference, Graphic, Point, Polyline, GeoJSONLayer, geometryEngine, projection ) => { let spatialReference; let view;
-
Set polygon and point styling that will display when a point is buffered on the map.
Use dark colors for code blocks "esri/views/MapView", "esri/Map", "esri/request", "esri/geometry/SpatialReference", "esri/Graphic", "esri/geometry/Point", "esri/geometry/Polyline", "esri/layers/GeoJSONLayer", "esri/geometry/geometryEngine", "esri/geometry/projection", ], ( MapView, Map, esriRequest, SpatialReference, Graphic, Point, Polyline, GeoJSONLayer, geometryEngine, projection ) => { let spatialReference; let view; const polySym = { type: "simple-fill", // autocasts as new SimpleFillSymbol() color: [150, 130, 220, 0.85], outline: { color: "gray", width: 0.5, }, }; const pointSym = { type: "simple-marker", // autocasts as new SimpleMarkerSymbol() color: "red", outline: { color: "white", width: 0.5, }, size: 5, };
Add a GeoJSON layer
To access the GeoJSONLayer class, you create a layer based on GeoJSON data. GeoJSON features are in the WGS84 geographic coordinate system. Once added to the map, the features are automatically projected to the match the spatial reference in the map view. To visualize the extent of the world for each spatial reference, a boundary graphic is displayed.
-
Create a boundary graphic. Set the
outline
property of thesymbol
to create a graydash
line. Set the geometrytype
to theextent
and thespatial
property toReference WG
.S84 Use dark colors for code blocks const pointSym = { type: "simple-marker", // autocasts as new SimpleMarkerSymbol() color: "red", outline: { color: "white", width: 0.5, }, size: 5, }; const projectionBoundary = { symbol: { type: "simple-fill", color: null, outline: { width: 0.5, color: [50, 50, 50, 0.75], style: "dash", }, }, geometry: { type: "extent", xmin: -180, xmax: 180, ymin: -90, ymax: 90, spatialReference: SpatialReference.WGS84, }, };
-
Create a
countries
element that instantiates the GeoJSONLayer class. Set theGeo Json renderer
to add a purple outline around the countries.Use dark colors for code blocks const projectionBoundary = { symbol: { type: "simple-fill", color: null, outline: { width: 0.5, color: [50, 50, 50, 0.75], style: "dash", }, }, geometry: { type: "extent", xmin: -180, xmax: 180, ymin: -90, ymax: 90, spatialReference: SpatialReference.WGS84, }, }; const countriesGeoJson = new GeoJSONLayer({ url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/World_Countries_(Generalized)/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson", copyright: "Esri", spatialReference: { wkid: 4326, }, renderer: { type: "simple", symbol: { type: "simple-fill", color: [255, 255, 255, 1], outline: { width: 0.5, color: [100, 70, 170, 0.75], }, }, }, });
-
Add the
countries
layer to theGeo Json map
.Use dark colors for code blocks const countriesGeoJson = new GeoJSONLayer({ url: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/ArcGIS/rest/services/World_Countries_(Generalized)/FeatureServer/0/query?where=1%3D1&outFields=*&f=geojson", copyright: "Esri", spatialReference: { wkid: 4326, }, renderer: { type: "simple", symbol: { type: "simple-fill", color: [255, 255, 255, 1], outline: { width: 0.5, color: [100, 70, 170, 0.75], }, }, }, }); const map = new Map({ layers: [countriesGeoJson], });
Set the spatial reference
The default spatial reference for a GeoJSON layer is WGS84 (4326). Use the selector to switch between WGS84 and Web Mercator (3857).
-
Create a
get
function that will return an instance of theSpatial Reference Spatial
class based on theReference wkid
that chosen from the selector.Use dark colors for code blocks const map = new Map({ layers: [countriesGeoJson], }); function getSpatialReference(wkid) { return new SpatialReference({ wkid: wkid, }); }
-
Assign the
wkid
variable to theSelect wkid
HTML element. Set thespatial
to the value of the wkid from the selector by calling theReference get
function.Spatial Reference Use dark colors for code blocks const map = new Map({ layers: [countriesGeoJson], }); const wkidSelect = document.getElementById("wkid"); spatialReference = getSpatialReference(wkidSelect.value);
-
Add an event listener to register a change based on the
event.target.value
in the selector.Use dark colors for code blocks const wkidSelect = document.getElementById("wkid"); spatialReference = getSpatialReference(wkidSelect.value); wkidSelect.addEventListener("change", (event) => { spatialReference = getSpatialReference(event.target.value); });
Switch between coordinate systems
Each time you select a new spatial reference, the view needs to be destroyed and recreated. Create a function that will reproject the view based on the chosen spatial reference.
-
Create a
center
point that will focus the view each time it reprojects. Set thelatitude
to30
andlongitude
to-10
. Set thespatial
toReference 4326
(WGS84).Use dark colors for code blocks wkidSelect.addEventListener("change", (event) => { spatialReference = getSpatialReference(event.target.value); }); let center = new Point({ latitude: 30, longitude: -10, spatialReference: { wkid: 4326, }, });
-
Create a
create
function.View With Spatial Reference Use dark colors for code blocks let center = new Point({ latitude: 30, longitude: -10, spatialReference: { wkid: 4326, }, }); function createViewWithSpatialReference(){ }
-
Add a conditional statement that will call the
destroy()
method if there is aview
present.Use dark colors for code blocks function createViewWithSpatialReference(){ if (view) { view.map = null; view.destroy(); } }
-
Set the
view
with a new instance of theMap
class. Set theView map
andspatial
properties with theReference map
and the chosenspatial
. Set theReference center
property with thecenter
point.Use dark colors for code blocks function createViewWithSpatialReference(){ if (view) { view.map = null; view.destroy(); } view = new MapView({ container: "viewDiv", map: map, spatialReference: spatialReference, center: center, //Need to use projection engine scale: 150000000, }); }
-
Add the
projection
and theBoundary wkid
elements to theSelect view UI
Use dark colors for code blocks view = new MapView({ container: "viewDiv", map: map, spatialReference: spatialReference, center: center, //Need to use projection engine scale: 150000000, }); view.graphics.add(projectionBoundary); view.ui.add(wkidSelect, "top-right");
-
Call the
create
method to display the GeoJSON layer when the application loads.View With Spatial Reference Use dark colors for code blocks let center = new Point({ latitude: 30, longitude: -10, spatialReference: { wkid: 4326, }, }); createViewWithSpatialReference();
-
Update the event listener to reproject the GeoJSON layer by calling the
create
function based on a new spatial reference.View With Spatial Reference Use dark colors for code blocks wkidSelect.addEventListener("change", (event) => { spatialReference = getSpatialReference(event.target.value); createViewWithSpatialReference(); });
-
Run the app. Switch between WGS84 and Web Mercator to ensure that the view is recreated each time.
Add more spatial references
There are many spatial references from which you can choose depending on the needs of your application. For a complete list of supported WKID's, visit Using spatial references.
-
Go to the projected coordinate system listings in the REST API documentation to find WKIDs of interest.
-
Update the selector to add other projected coordinate systems like:
- World Eckert VI (54010)
- World Sinusoidal (54008)
- World Fuller (54050)
Use dark colors for code blocks <option value="3857" disabled>Select a projection</option> <optgroup label="Equidistant (maintain length)"> <option value="4326" selected>WGS84 (GCS) -> pseudo Plate Carrée (Cylindrical)</option> <option value="54028">World Cassini (Cylindrical)</option> <option value="54027">World Equidistant conic (Conic)</option> </optgroup> <optgroup label="Conformal (maintain angles)"> <option value="54026">World Stereographic (Azimuthal)</option> </optgroup> <optgroup label="Equal-area (maintain area)"> <option value="54010">World Eckert VI (Pseudocylindrical)</option> <option value="54008">World Sinusoidal (Pseudocylindrical)</option> </optgroup> <optgroup label="Gnomonic (distances)"> <option value="102034">North Pole Gnomonic (Azimuthal)</option> </optgroup> <optgroup label="Compromise (distort all)"> <option value="3857">Web Mercator Auxiliary Sphere (Cylindrical)</option> <option value="54016">World Gall Stereographic (Cylindrical)</option> <option value="54042">World Winkel Tripel (Pseudoazimuthal)</option> <option value="54050">World Fuller / Dymaxion map (Polyhedral)</option>
-
Run the application and switch between the new coordinate systems.
-
Check the console. There will be an error that says: "#center" "incompatible spatialReference {"wkid":4326} with view's spatialReference {"wkid": "THE_SELECTED_WKID"}".
Re-project the center point
You can switch between WGS84 and Web Mercator because the geodetic coordinates of Web Mercator are defined by the WGS84 datum. The application failed when you switch between other projected coordinate systems because the coordinates of the center point, [30, -10], are not projected to the specified output spatial reference. By default, the view automatically projects geometries on the fly to match the spatial reference of the map. However, because the center point is not added as a graphic to the view, you need to use the projection engine to transform the coordinates based on what you choose from the selector.
-
Update the
create
function to be asynchronous so that itView With Spatial Reference awaits
for theprojection
engine toload
the engine's dependencies.Use dark colors for code blocks let center = new Point({ latitude: 30, longitude: -10, spatialReference: { wkid: 4326, }, }); createViewWithSpatialReference(); async function createViewWithSpatialReference() { await projection.load(); if (view) { view.map = null; view.destroy(); } view = new MapView({ container: "viewDiv", map: map, spatialReference: spatialReference, center: center, //Need to use projection engine scale: 150000000, }); view.graphics.add(projectionBoundary); view.ui.add(wkidSelect, "top-right"); }
-
Call the
project
method on thecenter
point based on thespatial
chosen from the selector.Reference Use dark colors for code blocks async function createViewWithSpatialReference() { await projection.load(); center = projection.project(center, spatialReference);
-
Re-run the app and choose different spatial references from the selector. The app will work because the center point coordinates are projected with each selection.
View projected coordinates
To visualize the effect of the reprojected center
point, add it as a graphic to the view and display its x/y coordinates from a new spatial reference.
-
Create a
display
function with aCoordinates point
parameter. Define apopup
to display theTemplate wkid
,x
, andy
coordinates of thepoint
. Set thedock
andOptions visible
to prevent a user from closing the popup.Elements Use dark colors for code blocks function getSpatialReference(wkid) { return new SpatialReference({ wkid: wkid, }); } function displayCoordinates(point) { const popupTemplate = { title: `WKID: ${point.spatialReference.wkid}`, content: `<b>X:</b> ${point.x.toFixed(5)} | <b>Y:</b> ${point.y.toFixed( 5 )}`, overwriteActions: true, }; view.popup.dockOptions = { buttonEnabled: false, }; view.popup.visibleElements = { closeButton: false, featureNavigation: false, }; }
-
Create a
graphic
. Set thegeometry
with thepoint
and thepopup
with theTemplate popup
defined in the previous step. Add theTemplate graphic
to theview
and call theopen
method to display the popup when the application loads.Use dark colors for code blocks function displayCoordinates(point) { const popupTemplate = { title: `WKID: ${point.spatialReference.wkid}`, content: `<b>X:</b> ${point.x.toFixed(5)} | <b>Y:</b> ${point.y.toFixed( 5 )}`, overwriteActions: true, }; view.popup.dockOptions = { buttonEnabled: false, }; view.popup.visibleElements = { closeButton: false, featureNavigation: false, }; const graphic = new Graphic({ geometry: point, popupTemplate: popupTemplate, }); view.graphics.add(graphic); view.openPopup({ features: [graphic], }); }
-
Update the
create
function. Call theView With Spatial Reference display
function with theCoordinates center
point as its parameterwhen
the view loads.Use dark colors for code blocks view = new MapView({ container: "viewDiv", map: map, spatialReference: spatialReference, center: center, //Need to use projection engine scale: 150000000, }); view.graphics.add(projectionBoundary); view.ui.add(wkidSelect, "top-right"); view.when(() => { displayCoordinates(center); });
-
Run the app. Switch between spatial references to view the projected coordinates of the center point.
View the effects of a projection
Each projection maintains the accuracy of one dimension, but creates inaccuracies in another. For example, you might be able to maintain area but not distance. To view the effects each spatial reference has on a circular shape, create a geodesic buffer where you move the mouse. The geodesic
method only works in WGS84 (4326) and Web Mercator (3857) spatial references. To view the buffer in another spatial reference, you need to reproject the point first to either 4326 or 3857 and then call the geodesic
method.
-
Create a
buffer
function that takes thePoint point
and theview
as its parameters. If thepoint
is in another spatial reference, then call theprojection
module and transform the coordinates into4326
to create the buffer. If there is nopoint
, thenreturn
.Use dark colors for code blocks function displayCoordinates(point) { const popupTemplate = { title: `WKID: ${point.spatialReference.wkid}`, content: `<b>X:</b> ${point.x.toFixed(5)} | <b>Y:</b> ${point.y.toFixed( 5 )}`, overwriteActions: true, }; view.popup.dockOptions = { buttonEnabled: false, }; view.popup.visibleElements = { closeButton: false, featureNavigation: false, }; const graphic = new Graphic({ geometry: point, popupTemplate: popupTemplate, }); view.graphics.add(graphic); view.openPopup({ features: [graphic], }); } function bufferPoint(point) { if ([3857, 4326].indexOf(point.spatialReference.wkid) === -1) { point = projection.project(point, getSpatialReference(4326)); if (!point) { return; } } }
-
Create a
buffer
element that calls thegeodesic
method on theBuffer point
. It will buffer thepoint
with a radius of1000
kilometers
.Use dark colors for code blocks function bufferPoint(point) { if ([3857, 4326].indexOf(point.spatialReference.wkid) === -1) { point = projection.project(point, getSpatialReference(4326)); if (!point) { return; } } const buffer = geometryEngine.geodesicBuffer(point, 1000, "kilometers"); }
-
Remove existing
graphics
from theview
except for the map projection boundary and center point graphics.Use dark colors for code blocks if (point && buffer) { // Avoid removing the map projection boundary view.graphics.removeMany([ view.graphics.getItemAt(2), view.graphics.getItemAt(3), ]); }
-
Create a
buffer
that takes theGraphic buffer
as itsgeometry
and thepoly
for itsSym symbol
styling. Add thebuffer
, along with theGraphic point
, created from moving the mouse, and itspoint
styling to theSym view
.Use dark colors for code blocks if (point && buffer) { // Avoid removing the map projection boundary view.graphics.removeMany([ view.graphics.getItemAt(2), view.graphics.getItemAt(3), ]); const bufferGraphic = { geometry: buffer, symbol: polySym, }; view.graphics.addMany([ bufferGraphic, { geometry: point, symbol: pointSym, }, ]); }
-
Create a
create
function that takes theBuffer event
andview
as its parameters. Define apoint
based on thex
andy
coordinates from theevent
. If there is apoint
, call thebuffer
function.Point Use dark colors for code blocks const graphic = new Graphic({ geometry: point, popupTemplate: popupTemplate, }); view.graphics.add(graphic); view.openPopup({ features: [graphic], }); } function createBuffer(event) { let point = view.toMap({ x: event.x, y: event.y, }); if (point) { bufferPoint(point); } }
-
Create an event handler that will buffer a point based on the movement of the mouse.
Use dark colors for code blocks view = new MapView({ container: "viewDiv", map: map, spatialReference: spatialReference, center: center, //Need to use projection engine scale: 150000000, }); view.graphics.add(projectionBoundary); view.ui.add(wkidSelect, "top-right"); view.when(() => { displayCoordinates(center); }); view.on("pointer-move", (event) => { createBuffer(event); });
Run the app
In CodePen, run your code to display the map.
When you run the app, you will see the center point and its coordinates. The features in the GeoJSON layer, as well as the geometries of the center point and buffer, are reprojected when you choose a new spatial reference. Move the mouse around the map to view the distortion for each spatial reference.
What's next?
Learn how to use additional API features and ArcGIS services in these tutorials: