Build a custom async
AMD module
Use Case:
Real-time earthquake heatmap
Advanced Tools
AMD Modules
Custom Classes
Promises
Extend
https://pixabay.com/en/army-blade-compact-cut-equipment-2186/
Advanced Tools
Stability
Scalability
Re-usability
Productivity!
https://pixabay.com/en/recycle-bin-container-recycling-24543/
Image CC0 Public Domain https://pixabay.com/en/spaghetti-bacon-pasta-894077/
Custom
AMD Modules
require(["esri/map", "dojo/domReady!"], function(Map) {
map = new Map("map", {
basemap: "topo",
center: [-122.45, 37.75],
zoom: 13
});
});
AMD modules...woohoo!
AMD Modules
Written in JavaScript
Logical subsets of functionality
Protect internal content
Portable/Reusable
Named Class
declare("namespace.ClassName", null, {
// Custom properties and methods
});
declare("namespace.ClassName", null, {
// Custom properties and methods
});
Name and namespace of Class
SuperClass(es)
var MyClass = declare(null, {
// Custom properties and methods
});
Anonymous Class
SuperClass(es)
Build a custom
real-time
earthquake map
TO-DO List
Retrieve the GeoJSON data
Convert GeoJSON to Graphics array
Inject array into a FeatureLayer
Apply HeatmapRenderer
https://pixabay.com/en/memo-note-yellow-post-is-670689/
Step 1 of 7
Create a file called
EarthquakeLayer.js
Note: Starts with capitalized letter
Step 2 of 7
Define EarthquakeLayer Module
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
Register the module
Tells loader to automatically load dependencies
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
_base/declare to define a Class via Prototypical Inheritance
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
Create a constructor - fires when Class is instantiated
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
Include esriRequest for retrieving GeoJSON
Create a constructor options Object
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
constructor: function(options){
}
}
)
});
Null value signifies no inheritance
Anonymous Class
define([
"dojo/_base/declare",
"esri/request"
], function(declare, esriRequest) {
return declare(null, {
url: null,
constructor: function(options){
this.url = options.url;
}
})
});
Add a url property for GeoJSON feed
getGeoJSON: function(){
var geoJSON = esriRequest({
"url" : this.url
});
return geoJSON;
}
Retrieve GeoJSON feed
esriRequest returns a Promise
Step 3 of 7
Parse the GeoJSON features
- Turn features into a Graphics Array
- Could have hundreds features
- Need to avoid jank!
- Parse GeoJSON in the background - use Web Worker!
Step 4 of 7
Initialize primary promise
Return primary promise
Initialize primary promise
Return primary promise
Parse features on background thread
Send to web worker
Parse features
Create array of Json Graphics
Return Json array to main thread
Initialize primary promise
Return primary promise
Parse features on background thread
Resolve primary promise
Return Json array to main thread
Convert Esri Json to Esri Graphics
Create parseFeatures()
Initialize web worker
Return a promise
parseFeatures: function(featureArray){
var dfd = new Deferred();
var worker = new Worker("libs/EarthquakeWorker.js");
return dfd.promise;
}
Post/receive messages from worker
worker.postMessage(
{
cmd: "parse",
features: featureArray.features
}
);
worker.onmessage = function(result){
worker.terminate();
};
worker.onerror = function(err){
console.log("Worker error: " + err.message);
};
post GeoJSON
receive esriJSON
A few notes
Worker + JSON size sweetspot
- Serialization/deserialization
- Earthquake GeoJSON ~ < 10 MBs
- Larger JSON files may block UI when passed to a Worker
Avoid JSON.parse()
Create Graphics from esriJSON
Resolve primary Promise
worker.onmessage = function(result){
worker.terminate();
var graphicsArr = [];
for(var i = 0; i < result.data.length; i++){
var graphic = new Graphic(result.data[i]);
graphicsArr.push(graphic);
}
dfd.resolve(graphicsArr);
};
Create feature collection using array
this.layerDefinition = {
"objectIdField": "id",
"geometryType" : "esriGeometryPoint",
"fields":[{
"name" : "id",
"alias" : "id",
"type" : "esriFieldTypeString"
},{
"name" : "depth",
"alias" : "depth",
"type": "esriFieldTypeInteger"
},{
"name" : "magnitude",
"alias" : "magnitude",
"type": "esriFieldTypeDouble"
}]
};
var featureCollection = {
"layerDefinition": this.layerDefinition,
"featureSet": {
"features": graphicsArr,
"geometryType": "esriGeometryPoint"
}
};
Create feature layer from feature collection
Apply HeatmapRenderer
try {
var featureLayer = new FeatureLayer(featureCollection);
var heatMapRenderer = new HeatmapRenderer({
field: "magnitude",
maxPixelIntensity: 250,
minPixelIntensity: 10,
blurRadius: 10
});
featureLayer.setRenderer(heatMapRenderer);
dfd.resolve(featureLayer);
}
catch(err){
dfd.resolve(false);
}
Chain it all together in init()
init: function(){
var dfd = new Deferred();
this.getGeoJSON()
.then(this.parseFeatures)
.then(this.createFeatureLayer.bind(this))
.then(function(result){
dfd.resolve(result);
});
return dfd.promise;
},
Step 5 of 7
Create web worker named
EarthquakeWorker.js
Step 6 of 7
Receive GeoJSON (message.data.features)
Parse the features
Post Esri JSON graphics array
onmessage = function(message) {
switch(message.data.cmd) {
case "parse":
var graphicsArr = parseFeaturesBackground(message.data.features);
postMessage(graphicsArr);
break;
}
};
Parse the features on background thread
function parseFeaturesBackground(features){
var graphicsArray = [];
for(var i = 0; i < features.length; i++){
try {
var graphicJson = {
"geometry":{
"x":features[i].geometry.coordinates[0],
"y":features[i].geometry.coordinates[1],
"spatialReference":{"wkid":4326}
},
"attributes":{
"id" : features[i].id,
"depth" : features[i].geometry.coordinates[2],
"magnitude" : features[i].properties.mag
}
};
graphicsArray.push(graphicJson);
}
catch(error){
console.log("Error creating graphic: " + error);
}
}
return graphicsArray;
}
In index.html create new EarthquakeLayer
Run init()
Add feature layer to map
map = new Map("map", {
basemap: "topo",
center: [-122.45, 37.75],
zoom: 3
});
esriConfig.defaults.io.corsEnabledServers.push("earthquake.usgs.gov");
var earthquakeLayer = new EarthquakeLayer({
url: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson"
});
earthquakeLayer.init().then(function(layer){
map.addLayer(layer);
});
Step 7 of 7
https://pixabay.com/en/graduate-graduation-school-student-150373/
https://pixabay.com/en/new-year-new-year-s-day-fireworks-152044/
Possible future enhancements
- Move earthquake retrieval to background thread
- Re-use a single webworker for parsing
- Make heat map properties public
- Include configurable background timer to auto-retrieve feed
Additional Resources
Andy Gup
agup@esri.com
@agup
John Gravois
jgravois@esri.com
@geogangster
Advanced Techniques - ArcGIS API for JavaScript (full)
By Andy G
Advanced Techniques - ArcGIS API for JavaScript (full)
- 1,816