Learning objectives:
At the end the lesson, learners should be able to:
- Understand how define a basemap for the map using a web map service.
- Understand how define a renderer to symbolize the features on the map.
- Understand how set up a popup window to display information about the features when clicked.
- Understand how add the feature layer to the map and display it.
- Understand how add a search widget to the map to allow users to search for locations.
- Understand how handle errors that may occur while loading the map or feature layer.
Overview lesson:
This code is written in JavaScript and utilizes the ArcGIS Maps SDK for JS for JavaScript to create a map and display data from a GeoJSON file. It creates a simple web map with a basemap and adds a layer for the GeoJSON data. The data is styled using a simple renderer to display points on the map. The map view is set to zoom and center on the data using the Extent class. Finally, a popup is added to display information about the data when a user clicks on a point on the map.
Requirements:
To run this code, you will need the following:
- To follow along with this tutorial, you will need a ArcGIS for Developers account and an API key for the ArcGIS Maps SDK for JavaScript. You can obtain an API key by following the instructions provided by ArcGIS for Developers or please visit beginner lesson page.
- A code editor such as Visual Studio Code, Sublime Text, Notepad++ or your favorite code editor.
- A web browser such as Google Chrome or Mozilla Firefox or your favorite browser.
- Basic knowledge of HTML, CSS, and JavaScript.
Step 1: Set up the HTML document
Step 1 involves setting up the HTML document that will be used to display the map. This is done by creating a basic HTML file with the necessary structure and including the required CSS and JavaScript files.
Please note that in this tutorial we use the API key. For more details on how to get the API key please check the beginning lesson.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title> ArcGIS Maps SDK for JavaScript - Advance</title>
<!-- Include CSS stylesheets -->
<link rel="stylesheet" href="https://js.arcgis.com/4.26/esri/themes/dark/main.css">
<!-- Include the ArcGIS JavaScript API -->
<script src="https://js.arcgis.com/4.26/"></script>
<style>
/* Define CSS styles for the map container and other elements */
html, body, #viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#timeSlider {
width: 400px;
}
#infoDiv {
height: 200px;
padding: 10px;
width: 280px;
}
#infoDiv span {
color: #F9C653;
font-size: 12pt;
font-weight: bolder;
}
.esri-time-slider__min-date,
.esri-time-slider__max-date,
.esri-time-slider__time-extent {
display: none !important;
}
</style>
</head>
<body>
<!-- The map container -->
<div id="viewDiv"></div>
<!-- The time slider container -->
<div id="timeSlider"></div>
<!-- The information panel -->
<div id="infoDiv" class="esri-widget">
<div><b>USGS Earthquakes</b></div>
<br />
<div id="statsDiv" class="esri-widget"></div>
</div>
<!-- JavaScript code for map setup and functionality -->
<script>
// JavaScript code goes here
</script>
</body>
</html>
In this example, we have created a basic HTML structure with the required elements for the map, time slider, and information panel. The CSS styles defined in the `<style>` section are used to customize the appearance of these elements.
The `<link>` tag includes the ArcGIS Maps SDK for JavaScript’s CSS stylesheet, while the `<script>` tag includes the JavaScript API itself. These dependencies are necessary for the functionality provided by the ArcGIS Maps SDK for JavaScript.
You can now proceed to the next steps to write the JavaScript code that sets up the map, handles data retrieval, and implements the time slider functionality.
Step 2: Load the ArcGIS Maps SDK for JavaScript modules
In order to use the features and functionality provided by the ArcGIS Maps SDK for JavaScript, you need to load the required modules. These modules include classes for creating a map, adding layers, and working with widgets.
In this code snippet, we use the `require` function to load the required modules asynchronously. The modules are specified as an array of strings, with each string representing the module’s path. The callback function is executed once the modules are loaded successfully. Inside the callback function, you can write your JavaScript code to set up the map and implement the desired functionality.
The required modules in this example include:
– `esri/Map`: Represents a map instance.
– `esri/views/MapView`: Provides a view for rendering the map.
– `esri/layers/GeoJSONLayer`: Allows you to add a GeoJSON layer to the map.
– `esri/widgets/TimeSlider`: Enables the time slider widget for filtering features based on time.
– `esri/widgets/Expand`: Provides the expand widget for collapsing and expanding other widgets.
– `esri/widgets/Legend`: Displays a legend for the map’s layers.
In the `<script>` section of your HTML document, add the following code to load the necessary modules:
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/Graphic",
"esri/layers/GraphicsLayer",
"esri/widgets/BasemapToggle",
"esri/widgets/BasemapGallery",
"esri/widgets/Expand"
], function(Map, MapView, Graphic, GraphicsLayer, BasemapToggle, BasemapGallery, Expand) {
// Code goes here
});
</script>
With the modules loaded, you can proceed to the next step to create the map and configure its layers.
Step 3: Create a map and view
Once you have loaded the necessary modules, you can proceed to create a map instance and a view to visualize the map. The map represents the overall container for layers and other map elements, while the view defines how the map is displayed on the web page.
Inside the callback function of the `require` statement, add the following code to create a map and view:
const map = new Map({
basemap: "gray-vector",
layers: [layer]
});
const view = new MapView({
map: map,
container: "viewDiv",
zoom: 5,
center: [74.5898, 42.87465]
});
In this code snippet, we first create a new `Map` instance by specifying the basemap and layers. The `basemap` property sets the base layer for the map. In this example, we use the “gray-vector” basemap, which provides a gray-scale map background. The `layers` property accepts an array of layers to be added to the map. Here, we add the `layer` object, which represents the GeoJSON layer loaded from the specified URL.
Next, we create a `MapView` instance by providing the `map` object and specifying the `container` property as the ID of the HTML element where the map view should be rendered. In this case, we use the “viewDiv” element. Additionally, we set the initial `zoom` level and `center` coordinates to focus the map on a specific location.
With the map and view set up, you can move on to the next step to add the time slider widget and implement its functionality.
In this step, we will create markers to represent features on the map and configure popups to display information when a marker is clicked. We will also perform related analysis based on the time slider’s current time extent.
Inside the callback function of the `require` statement, add the following code:
// Create a GeoJSON layer to represent the markers
const layer = new GeoJSONLayer({
url: "https://gist.githubusercontent.com/ZhibekSolp/1ec7b488e7b67c793ecc43edf8ccc3b9/raw/01c6786bbff65fcb53a823900052e6b03dda6aa9/1970.geojson",
// ... (other GeoJSONLayer configurations)
});
// ...
// Watch for time slider timeExtent change
timeSlider.watch("timeExtent", () => {
layer.definitionExpression = "time <= " + timeSlider.timeExtent.end.getTime();
// Perform analysis and update popups based on the current time extent
// ...
});
In the provided code snippet, we create a `GeoJSONLayer` object to represent the markers on the map. The `url` property specifies the URL from which the GeoJSON data is loaded. You can replace the URL with your own GeoJSON data source.
Next, we use the `watch` method to monitor changes to the `timeExtent` property of the `timeSlider`. Whenever the `timeExtent` changes, the callback function is triggered. Inside the callback function, we update the `definitionExpression` of the `layer` to filter the features based on the current time extent of the time slider.
Additionally, you can perform related analysis and update the popups based on the current time extent within the callback function. This may involve querying features within the time extent and updating the popup content dynamically. The specific analysis and popup update logic will depend on your requirements and the available data.
With these additions, you have created markers representing features, configured popups, and implemented related analysis based on the time slider’s current time extent.
Step 5: Add and change the base map of a map
In this step, we will add the ability to change the base map of the map by providing a selection of different base maps. This allows users to switch between different map styles or imagery as per their preference.
Inside the callback function of the `require` statement, add the following code:
// Create a basemap gallery widget
const basemapGallery = new BasemapGallery({
view: view,
container: "basemapGallery"
});
// Add the basemap gallery widget to the top-right corner of the view
view.ui.add(basemapGallery, "top-right");
// Listen for basemap change event
basemapGallery.watch("activeBasemap", (newValue) => {
map.basemap = newValue;
});
In the provided code snippet, we create a `BasemapGallery` widget, which provides a collection of base maps that users can choose from. We specify the `view` and `container` properties to associate the widget with the map view and set the HTML element where the widget should be rendered.
Next, we add the `basemapGallery` widget to the top-right corner of the view using the `view.ui.add` method.
Finally, we listen for the `activeBasemap` property change event of the `basemapGallery`. When the user selects a different base map from the gallery, the `newValue` parameter of the event callback represents the newly selected basemap. We update the `map.basemap` property to set the selected basemap for the map.
By implementing these additions, users will have the ability to switch between different base maps using the `BasemapGallery` widget, enhancing the map’s flexibility and customization options.
Step 6: Final step. Save HTML
Finally, the code ends with an HTML element which specifies where the map will be displayed on the web page. Save the HTML file and open it in your favorite web browser. Next you can find full code:
Note: that the code also includes comments which explain what each section of code does. These comments are denoted by the //
symbol.
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>
ArcGIS Maps SDK for JavaScript - Advance
</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#timeSlider {
width: 400px;
}
#infoDiv {
height: 200px;
padding: 10px;
width: 280px;
}
#infoDiv span {
color: #F9C653;
font-size: 12pt;
font-weight: bolder;
}
.esri-time-slider__min-date,
.esri-time-slider__max-date,
.esri-time-slider__time-extent
{display:none !important;}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.26/esri/themes/dark/main.css"
/>
<script src="https://js.arcgis.com/4.26/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/GeoJSONLayer",
"esri/widgets/TimeSlider",
"esri/widgets/Expand",
"esri/widgets/Legend"
], (Map, MapView, GeoJSONLayer, TimeSlider, Expand, Legend) => {
let layerView;
// set the timeInfo on GeoJSONLayer at the time initialization
const layer = new GeoJSONLayer({
url: "https://gist.githubusercontent.com/ZhibekSolp/1ec7b488e7b67c793ecc43edf8ccc3b9/raw/01c6786bbff65fcb53a823900052e6b03dda6aa9/1970.geojson",
copyright: "USGS Earthquakes",
title: "USGS Earthquakes",
// set the CSVLayer's timeInfo based on the date field
timeInfo: {
startField: "time", // name of the date field
interval: {
// set time interval to one years
unit: "years",
value: 1
}
},
orderBy: {
field: "mag"
},
renderer: {
type: "simple",
field: "mag",
symbol: {
type: "simple-marker",
color: "orange",
outline: null
},
visualVariables: [
{
type: "size",
field: "mag",
stops: [
{
value: 4,
size: "5px"
},
{
value: 5,
size: "15px"
},
{
value: 6,
size: "25px"
}
]
},
{
type: "color",
field: "depth",
stops: [
{
value: 5,
color: "#C53C06",
label: "<5km"
},
{
value: 10,
color: "#F8864D",
label: "10km"
},
{
value: 25,
color: "#F9C653",
label: ">25km"
}
]
}
]
},
popupTemplate: {
title: "{title}",
content: [
{
type: "fields",
fieldInfos: [
{
fieldName: "place",
label: "Location",
visible: true
},
{
fieldName: "mag",
label: "Magnitude",
visible: true
},
{
fieldName: "depth",
label: "Depth",
visible: true
}
]
}
]
}
});
const map = new Map({
basemap: "gray-vector",
layers: [layer]
});
const view = new MapView({
map: map,
container: "viewDiv",
zoom: 5,
center: [74.5898, 42.87465]
});
// create a new time slider widget
// set other properties when the layer view is loaded
// by default timeSlider.mode is "time-window" - shows
// data falls within time range
const timeSlider = new TimeSlider({
container: "timeSlider",
playRate: 50,
stops: {
interval: {
value: 1,
unit: "hours"
}
}
});
view.ui.add(timeSlider, "bottom-left");
// wait till the layer view is loaded
view.whenLayerView(layer).then((lv) => {
layerView = lv;
// start time of the time slider - January 1, 1970
const start = new Date(1970, 0, 1);
// set time slider's full extent to
// January 1, 1970 until end date of layer's fullTimeExtent
timeSlider.fullTimeExtent = {
start: start,
end: layer.timeInfo.fullTimeExtent.end
};
let end = new Date(start);
end.setDate(end.getDate() + 1);
// timeExtent property is set so that timeslider
// widget show the first day. We are setting
// the thumbs positions.
timeSlider.timeExtent = { start, end };
});
// watch for time slider timeExtent change
timeSlider.watch("timeExtent", () => {
layer.definitionExpression =
"time <= " + timeSlider.timeExtent.end.getTime();
layerView.featureEffect = {
filter: {
timeExtent: timeSlider.timeExtent,
geometry: view.extent
},
excludedEffect: "grayscale(20%) opacity(12%)"
};
// run statistics on earthquakes fall within the current time extent
const statQuery = layerView.featureEffect.filter.createQuery();
statQuery.outStatistics = [
magMax,
magAvg,
magMin,
tremorCount,
avgDepth
];
layer
.queryFeatures(statQuery)
.then((result) => {
let htmls = [];
statsDiv.innerHTML = "";
if (result.error) {
return result.error;
} else {
if (result.features.length >= 1) {
const attributes = result.features[0].attributes;
for (name in statsFields) {
if (attributes[name] && attributes[name] != null) {
const html =
"<br/>" +
statsFields[name] +
": <b><span> " +
attributes[name].toFixed(2) +
"</span></b>";
htmls.push(html);
}
}
const yearHtml =
"<span>" +
result.features[0].attributes["tremor_count"] +
"</span> earthquakes were recorded between " +
(1970 + (timeSlider.timeExtent.start.getTime() / (1000 * 60 * 60 * 24 * 365))).toFixed(0) +
" - " +
(2023 + (timeSlider.timeExtent.end.getTime() / (1000 * 60 * 60 * 24 * 365))).toFixed(0) +
".<br/>";
if (htmls[0] == undefined) {
statsDiv.innerHTML = yearHtml;
} else {
statsDiv.innerHTML =
yearHtml + htmls[0] + htmls[1] + htmls[2] + htmls[3];
}
}
}
})
.catch((error) => {
console.log(error);
});
});
const avgDepth = {
onStatisticField: "depth",
outStatisticFieldName: "Average_depth",
statisticType: "avg"
};
const magMax = {
onStatisticField: "mag",
outStatisticFieldName: "Max_magnitude",
statisticType: "max"
};
const magAvg = {
onStatisticField: "mag",
outStatisticFieldName: "Average_magnitude",
statisticType: "avg"
};
const magMin = {
onStatisticField: "mag",
outStatisticFieldName: "Min_magnitude",
statisticType: "min"
};
const tremorCount = {
onStatisticField: "mag",
outStatisticFieldName: "tremor_count",
statisticType: "count"
};
const statsFields = {
Max_magnitude: "Max magnitude",
Average_magnitude: "Average magnitude",
Min_magnitude: "Min magnitude",
Average_depth: "Average Depth"
};
// add a legend for the earthquakes layer
const legendExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Legend",
view: view,
content: new Legend({
view: view
}),
expanded: false
});
view.ui.add(legendExpand, "top-left");
const statsDiv = document.getElementById("statsDiv");
const infoDiv = document.getElementById("infoDiv");
const infoDivExpand = new Expand({
collapsedIconClass: "esri-icon-collapse",
expandIconClass: "esri-icon-expand",
expandTooltip: "Expand earthquakes info",
view: view,
content: infoDiv,
expanded: true
});
view.ui.add(infoDivExpand, "top-right");
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="timeSlider"></div>
<div id="infoDiv" class="esri-widget">
<div><b>USGS Earthquakes</b></div>
<br />
<div id="statsDiv" class="esri-widget"></div>
</div>
</body>
</html>
Conclusion
In conclusion, the provided HTML code demonstrates the integration of the ArcGIS Maps SDK for JavaScript to create an interactive map application. The code showcases features such as displaying earthquake data using a GeoJSONLayer, implementing a TimeSlider for temporal filtering, performing analysis on the data, and providing additional information through popups and legends. The code also allows users to change the base map using the BasemapGallery widget.
Further exploration and customization can be done to enhance the functionality and visual appeal of the map application. Some of the key concepts covered from this lesson include:
- Adding additional layers: You can incorporate more layers, such as tiled layers, feature layers, or imagery layers, to overlay different types of data on the map.
- Customizing symbology: Modify the symbol styles and visual variables to customize the appearance of the earthquake markers based on different attributes.
- Incorporating user interaction: Implement user interaction features like zooming, panning, and querying data to allow users to explore and interact with the map in more detail.
- Introducing spatial analysis: Explore and integrate spatial analysis capabilities, such as buffering, spatial queries, or proximity analysis, to derive meaningful insights from the data.
- Enhancing the UI/UX: Improve the user interface by adding additional widgets, controls, or interactive elements to enhance the overall user experience.
To further explore and expand on the concepts covered in this lesson, learners can refer to the following resources:
- ArcGIS API for JavaScript Documentation: https://developers.arcgis.com/javascript/latest/
- ArcGIS API for JavaScript Documentation Sample: https://developers.arcgis.com/javascript/latest/sample-code/
- ArcGIS Online Help: https://developers.arcgis.com/javascript/latest/community/#follow-us-on-twitter
- Esri Training: https://www.esri.com/training/
- USGS Earthquake Catalog: https://earthquake.usgs.gov/earthquakes/