GIS Programming with ESRI

Geographic Information System (GIS) Programming is the software development for using and making maps. The technologies that enable GIS software are the Geospatial Database (GDB) and the map server. GDBs are SQL relational DBs with the ability to make geometric/spatial queries, for example selecting all the GDB entries within a specific area. GDBs can make these spatial queries efficiently, for example log-linear time.

Although open source GIS technologies exist, the field is new and expanding, and it is dominated by commercial software companies. Esri is one of the leading GIS software companies, particularly among government and research scientists. Esri offers an array of technology including ArcGIS JavaScript (JS) which is the platform for developing web based GIS applications. Currently, Esri is transitioning from ArcGIS JS version 3 to version 4. ArcGIS JS version 3 offers all the capabilities for working with 2 dimensional maps. ArcGIS JS version 4 extends version 3 to working with 3 dimensional maps, but does not offer all the widgets available in version 3. This tutorial will use ArcGIS JS version 3 because the API is more complete for 2 dimensional maps and there are more resources on the web. The primary resource for ArcGIS JS v3 is Esri development website:

https://developers.arcgis.com/javascript/3/

This lecture is composed of three tutorials. The tutorials will demonstrate how to programmatically load a map into webpage, interact with the map by drawing features, and give the features attributes which are stored in the GDB. In the process you will learn how to make spatial queries, display the queries on the map and manipulate the webpage Document Object Model (DOM).

Maps and Layers Tutorial

In ArcGIS JS v3 the Map is the primary object for containing, viewing and interacting with maps. A Map is composed of one or more Layer. The layers are stacked on top of each other. As the code loads the layers into the map, their attributes can be specified and modified. Esri offers several maps that can be used as base maps. This tutorial will demonstrate how to create a map, change the base maps and add historical map layers on top of the base maps so that current towns of Calumet and Laurium can be visually compare with Calumet and Laurium of 1949.

In this tutorial, you will learn:

  • Creating a Map using Esri Basemaps
  • Adding historical map layers

A good tutorial at ArcGIS JS v3 to study is

https://developers.arcgis.com/javascript/3/jshelp/intro_firstmap_amd.html

Dojo

ArcGIS JS uses the Dojo JavaScript framework.

https://dojotoolkit.org/

Dojo is much like JQuery and offers all the tools for manipulating the DOM and making interactive web pages. There are many good reasons for using a JS framework. They make standard task easy to code, and more important, JS frameworks insure that the JS code works across browsers.

In addition Dojo uses an Asynchronous Module Definition (AMD) for managing JS packages.

https://dojotoolkit.org/documentation/tutorials/1.10/hello_dojo/index.html

You will not be making packages, but you will be using many Dojo and Esri packages. Using Dojo AMD package manager, only requires the uses of the “require” statement with two arguments, which I call the “require-array” for loading the packages and the “require-callback” function which gives the package variable names to use in your script.

<script>
require([ "package-name-1", "package-name-2"], function (name1, name2) {
 // your script goes here using name1 and name2
});
</script>

You can give the packages any variable name you wish in the callback function argument list. The order of the variable names in the require-callback determines which package it is associated with. While developing the code, I format the require-array and require-callback arguments vertically, and when I choose to add a new package, I add the package name and variable name to the bottom of both lists. This minimizes confusion and coding errors. In addition, I give the variable name a very similar name to the package name.

Some packages do not require arguments in the require-callback, for example they can add additional features to other packages. A special package, “dojo/domReady!”, calls the require-callback after the webpage has been loaded on the webpage. I put the “dojo/domReady!” package name last in the require-array. It does not need a require-callback argument.

We are ready to look at the code.

The Code

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>Map and Layers</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/dijit/themes/claro/claro.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/esri/css/esri.css">
    <style>
        html, body, #map {
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
    <script src="https://js.arcgis.com/3.18/"></script>
    <script>
        var map;

        require( ["esri/map",
                "esri/dijit/BasemapGallery",
                "dojo/parser", // for parsing the data-dojo-***
                "esri/layers/ArcGISDynamicMapServiceLayer",

                "dijit/layout/BorderContainer",
                "dijit/layout/ContentPane",
                "dijit/TitlePane",
                "dojo/domReady!"
        ],

        function(Map,
                 BasemapGallery,
                 parser,
                 ArcGISDynamicMapServiceLayer
                 )
        {
            parser.parse() // this is for data-dojo-type
            map = new Map("mapDivId", {
                basemap: "satellite", //"topo",  //For full list of pre-defined basemaps, navigate to http://arcg.is/1JVo6Wd
                center: [-88.448, 47.242], // longitude, latitude
                zoom: 16,
                logo: false
            });

            map.on("click", function (evt) {
                console.log("map on click evt", evt);
                console.log("mapevt.mapPoint.x="+evt.mapPoint.x+", evt.mapPoint.y="+evt.mapPoint.y);
            });

            var basemapGallery = new BasemapGallery({
                showArcGISBasemaps: true,
                map: map
            }, "basemapGallery");
            basemapGallery.startup();

            basemapGallery.on("load", function(){
                for(var i = 0, len = basemapGallery.basemaps.length; i < len; i++){
                    console.log("basemaps")
                    console.log("basemap ",i, basemapGallery.basemaps[i])
                };
                // Can only remove after the Basemap Gallery is looaded
                basemapGallery.remove("basemap_11");
                basemapGallery.remove("basemap_9");
                basemapGallery.remove("basemap_8");
//                basemapGallery.remove("basemap_7");
                basemapGallery.remove("basemap_6");
                basemapGallery.remove("basemap_5");
                basemapGallery.remove("basemap_4");
                basemapGallery.remove("basemap_3");
                basemapGallery.remove("basemap_1");
                basemapGallery.remove("basemap_0");
            });

            basemapGallery.on("error", function(msg) {
                console.log("basemap gallery error:  ", msg);
            });

            map.on("layer-add", function(evt){
                console.log("layer-add evt", evt);
            });

            var CalumetTiledServiceURL = "http://gis-core.sabu.mtu.edu:6080/arcgis/rest/services/KeweenawHSDI/Cal49FIPS_core/MapServer";
            var historicCalumetMapLayer = new ArcGISDynamicMapServiceLayer(CalumetTiledServiceURL);
            historicCalumetMapLayer.on("error",function(error){
                alert ("There is a problem on loading:" + CalumetTiledServiceURL);
            });
            historicCalumetMapLayer.setOpacity(0.7);
            historicCalumetMapLayer.on("error",function(error){
                alert ("There is a problem on loading:" + LauriumServiceURL);
            });
            map.addLayer(historicCalumetMapLayer);


            var LauriumServiceURL = "http://gis-core.sabu.mtu.edu:6080/arcgis/rest/services/KeweenawHSDI/Laurium49FIPS/MapServer";
            var historicLauriumMapLayer = new ArcGISDynamicMapServiceLayer(LauriumServiceURL);
            historicLauriumMapLayer.on("error",function(error){
                alert ("There is a problem on loading:" + LauriumServiceURL);
            });
            historicLauriumMapLayer.setOpacity(0.7);
            map.addLayer(historicLauriumMapLayer);

        });
    </script>
</head>

<body class="claro">
<div data-dojo-type="dijit/layout/BorderContainer"
     data-dojo-props="design:'headline', gutters:false"
     style="width:100%;height:100%;margin:0;">

    <div id="mapDivId"
         data-dojo-type="dijit/layout/ContentPane"
         data-dojo-props="region:'center'"
         style="padding:0;">

        <div style="position:absolute; right:20px; top:10px; z-Index:999;">
            <div data-dojo-type="dijit/TitlePane"
                 data-dojo-props="title:'Switch Basemap', closable:false, open:false">
                <div data-dojo-type="dijit/layout/ContentPane" style="width:380px; height:130px; overflow:auto;">
                    <div id="basemapGallery"></div>
                </div>
            </div>
        </div>

    </div>
</div>
</body>

</html>

You can view the webpage here or from resources/gis-programs/maps_layers.html. Copy the source from “view page source” and save it your local machine. You can loadit into your browser using localhost. In addition, you can make an IntelliJ Static Web project and use the IDEA to edit the html and script. IntelliJ makes it very easy to check the code on multiple browsers. In the editing a window, an array of browser icons appear as you hover the cursor near the upper right corner. Click on a browser icon in the array to load the webpage into the browser of your choosing.

Creating Maps

A Esri JS code sample for demonstrating creating a map also explains the dojo AMD require syntax.

https://developers.arcgis.com/javascript/3/jssamples/map_simple.html

A map is created with Map constructor.

map = new Map("mapDiv", options)

The first argument of the Map constructor is a string identifying the id of the tag to load the map into. The options is an object with key-value pairs. See the Map API for all the available options:

https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#map1

This code makes use of the basemap, center, zoom and logo options. The basemap specifies a base map to load first. The center specifies the longitude and latitude coordinates to center the map on, and the zoom specifies the zoom level. These levels are specified by the base map. Finally the “logo: false” specifies to load the map without the “Esri” logo. It is true by default.

Events and JS

JS is asynchronous and event driven. Generally, the JS code runs in a single thread. Some operations requires time, for example requesting an image to load into the web page or waiting for the user to interact with the webpage, so Java splits this task off of by making an event and adding the event listener. When the task completes the event handler is placed in a queue. The JS checks the event queue when the script end and calls the event handler.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

There are two primary syntaxes for handling events. One way is to give the task a callback function in the arguments of the function requesting the task. The callback function is called after the task completes. Using this technique the event firing and listening is handled in the background by the JS engines. Another popular technique is to create events and specify functions to listen to the event firing. The code fires the events and the event managing package runs the functions listening to event. In Dojo, the events are given names, and Dojo uses the “on” function to set the event listener. The callback function in the “on” function is typically an anomalous function. For example

map.on("click", function (evt) {
 console.log("map on click evt", evt);
 console.log("mapevt.mapPoint.x="+evt.mapPoint.x+", evt.mapPoint.y="+evt.mapPoint.y);
});

the “on” function specifies the event name and an anomalous function to log the event object, evt, to the JS console after the map has been click.

Load the webpage into a browser and click on the map. After loading the page, open the browser’s development tools. In Chrome, this is done by right clicking on the webpage and selecting “inspect.” After the development tools loads into a panel, make sure that the console is showing by clicking on the “console” tab. You can inspect the object in the console window by clicking on the object. It will expand and list all the properties.

You can learn more about the Map fire event at

https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#events

Base Map Gallery

The Basemap Gallery is a dijit, meaning it is widget using Dojo.

http://dojotoolkit.org/reference-guide/1.10/dijit/

The Basemap Gallery constructor arguments are reverse from the map constructor, meaning first an object representing the key-value parameters and then the id of the html tag of where to render the widget.

http://dojotoolkit.org/reference-guide/1.10/dijit/

The map variable must be specified in the parameter object.

Inspect the tags in the body surrounding the basemapGallery div. They use standard css styling and data-dojo-*** attributes to style, layout add interactivity to the base map gallery.

https://dojotoolkit.org/documentation/tutorials/1.10/declarative/
http://dojotoolkit.org/reference-guide/1.10/dijit/layout.html

The dojo/parser module object parses the DOM, looking for data-dojo-*** tags and decorating them.

https://dojotoolkit.org/reference-guide/1.10/dojo/parser.html

This is the reason for calling “parser.parse()” at the top of the script.

Many of the base maps provided by the basemap gallery, I do not care for, so the code removes some of them after the basemap gallery has been loaded.

Historical Maps

Professor Don LaFreniere has digitized many Keweenaw historical maps. You can see the list at

http://gis-core.sabu.mtu.edu:6080/arcgis/rest/services/KeweenawHSDI

The names of the maps are specified by an abbreviated town name, year and type of maps. For example the Calumet 1917 map is named “Cal17FIPS”.

The “FIPS” maps were made from scans of historical paper maps. Clicking on a FIPS map link will take you to specification of the map. After clicking on a map link, the url appearing in the browser’s navigation field is the url the code will use to request the map. To see the map, click on the “View In: ArcGIS JavaStript” link in the specification page for the map.

To load a historical map on to map, the code must create a map service layer and then call map.addlayer(…) on the layer.

https://developers.arcgis.com/javascript/3/jsapi/layer-amd.html

There are many Esri map service layers, but the two we can use for the historical maps are ArcGISDynamicMapServiceLayer and ArcGISTiledMapServiceLayer.

https://developers.arcgis.com/javascript/3/jsapi/arcgisdynamicmapservicelayer-amd.html
https://developers.arcgis.com/javascript/3/jsapi/arcgistiledmapservicelayer-amd.html

The titled map server is generally preferred because it caches the maps and therefore loads into the browser faster. I could not get it work with the base maps because of a miss match in the zoom levels between the base maps and the historical maps. Because the dynamic map service does not cache the map, it can modify the map match the zoom level of the base map.

Before adding the historical map layer, the code set opacity of the map

https://developers.arcgis.com/javascript/3/jsapi/layer-amd.html#setopacity

The default opacity is 1. An opacity 1 would completely hide the base map and the viewer would not be able to see current map. An opacity of 0 would make the historical map completely transparent and the viewer would not be able to the historical map. I wanted the base map to be predominate so I chose a value close to 1.

After a layer is added to the map, the map fires the “layer-add” event. A listener for the event is specified in the callback for the map.on(…) method.

map.on("layer-add", function(evt){
 console.log("layer-add evt", evt);
});

In this case, the script just prints the event to the console.

Play with the map and try modifying the script. Be sure to look at the JS console output. For extra credit, try to get the historical maps to be a titled map service layer and working with the base maps. The base maps are layers that have more scales then the historical maps. You could try setting the minimum and maximum scale of the base maps after they have been loaded.

Features Tutorial

This tutorial show how users can draw markers on a layer added to the map and view the information of markers added to the layer.

In this tutorial, you will learn about:

  • FeatureLayers
  • Graphic
  • Drawing
  • Controlling the Tooltip

Good references for this tutorial are the Esri sample code.

https://developers.arcgis.com/javascript/3/samples/ed_simpletoolbar/
https://developers.arcgis.com/javascript/3/samples/ed_feature_creation/

Feature Layer

The feature layer is the primary means for a map to interact with the GDB.

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html

It is associated with a table in the GDB via the URL of a Feature Server. Because cartographer make maps by drawing graphics, a feature layer inherit from graphic layer.

https://developers.arcgis.com/javascript/3/jsapi/graphicslayer-amd.html

So a feature layer is also a graphic layer. Points, polylines and polygons are Graphic objects added to graphic layer.

https://developers.arcgis.com/javascript/3/jsapi/graphic-amd.html

A graphic can have geometry, symbol, attributes and infoTemplate. The geometry specifies shape and location of the graphic. The symbol specifies how to render the graphic in the graphic layer. For graphics in a feature layer, the attributes represent an entry in the GDB table of the feature layer. The attribute is a key-value object associated the table field names with values for the fields.

A diagram of the relationships

FeatureLayer -------------> Table in the GDB
has Graphic ---------------> has entry
   Geometry --------------------> shape and location 
   Attribute -------------------> field values
is GraphicLayer-------------> Layer in the Map
   Symbol -----------------------> how to render the geometry
   InfoTemplate -----------------> how to display attributes

The FeatureLayer, its Graphics and the Graphics’ Geometries and Attributes are the key components for working with a GIS and the GDB. I believe the terminology would have been a little less confusing if FeatureLayers had Features with Attributes and Geometry instead of Graphics. I continuously remind myself that “Graphics are Features”. The Esri API does not have a Feature, so I also remind myself that “Features are Graphics”. The ArcGIS JS v4 has not rectified this confusing terminology.

The Geometry of a graphic can be one of five different types

  • Point – has x and y map coordinates
  • Multipoint – is a collection of points
  • Polyline – has points and the segments between the points
  • Polygon – has Polylines that enclose an area
  • Extent – is a rectangular area

The point, polyline and polygon are fundamental geometric concepts. The Multipoint is necessary because a single feature could require multiple points to be defined but is not a curve or area. The
Extent is special and is included because rectangular area is frequently used to approximate Multipoint, Polyline and Polygon in geospatial queries.

A feature layer can have only one of these geometric types, meaning that all the graphics/features in the layer must be of the same geometry type. So if you are constructing a map with points, curves and areas, you will need three feature layers, one for Points, one for Polylines and one for Polygons.

When the attributes for a feature/graphic represent an entry in to the GDB table, all the features/graphics have the same set of attributes albeit the attributes can have null values. For example suppose all the features are points but represent different types of point such as the location of fire hydrants and signs along the road. These features can be contained in a single feature layer even though the signs might have an attribute such as text and hydrant might have the attribute capacity. The entire attribute set for the feature layer would contain both text and capacity. You may not want to do this, but you can.

A mental model that use for a feature layer is that it is a table of attributes for all the features/graphics of one geometric type. The table also contains the geometric representations for the features which is commonly displayed has graphical map layer.

This tutorial uses a ArcGIS feature server made by Professor Don Lafreniere.

http://services2.arcgis.com/RPhrOu9XQzI31xTa/arcgis/rest/services/Robert_Point_Test/FeatureServer

Point your browser at the URL above and you will see specification for the server. This server is severing only a single layer so clicking on either “All Layers and Tables” or “Roberttest(0)” will lead to pages with the same information. The interesting information:

  • “Geomtery Type: esriGemoteryPoint” saying that the layer is for points
  • “Drawing Info:” which is default symbol for the graphic
  • “Has Attachments: true” which says that the features can have attachments, more on this in the next tutorial
  • “Object ID Field: OBJECTID” which is the auto incremented unique id field for the table
  • “Fields” which is a list of the fields/attributes for the table. The first word is the name of the field.

The field names are

  • OBJECTID
  • shortint
  • longint
  • text50
  • GlobalID

They are rather arbitrary and suggest their types.

We are ready to look at the code.

The Code

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>Draw Features</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/dijit/themes/claro/claro.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/esri/css/esri.css">
    <style>
        html, body, #map {
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
    <script src="https://js.arcgis.com/3.18/"></script>
    <script>

//        var featureLayer;

        require(["esri/map",
                "esri/layers/FeatureLayer",
                "esri/toolbars/draw",
                "esri/symbols/SimpleMarkerSymbol",
                "esri/Color",
                "esri/graphic",
                "esri/renderers/SimpleRenderer",
                "esri/InfoTemplate",
                "dojo/query",

                "dojo/domReady!"
        ],
        function (Map,
                  FeatureLayer,
                  Draw,
                  SimpleMarkerSymbol,
                  Color,
                  Graphic,
                  SimpleRenderer,
                  InfoTemplate,
                  dojoQuery
        ) {
            var map;

            // Create the map with a basemap
            map = new Map("map", {
                basemap: "osm", //"topo",  //For full list of pre-defined basemaps, navigate to http://arcg.is/1JVo6Wd
                center: [-88.448, 47.242], // longitude, latitude
                zoom: 16,
                logo: false
            });

            // create Feature Layer and add
            var featureURL = "http://services2.arcgis.com/RPhrOu9XQzI31xTa/arcgis/rest/services/Robert_Point_Test/FeatureServer/0";
            var featureLayer = new FeatureLayer(featureURL, {
                mode: FeatureLayer.MODE_ONDEMAND,
                outFields: ["*"]
            });

            var marker = new SimpleMarkerSymbol(); // default is a circle
//            marker.setPath("M16,3.5c-4.142,0-7.5,3.358-7.5,7.5c0,4.143,7.5,18.121,7.5,18.121S23.5,15.143,23.5,11C23.5,6.858,20.143,3.5,16,3.5z M16,14.584c-1.979,0-3.584-1.604-3.584-3.584S14.021,7.416,16,7.416S19.584,9.021,19.584,11S17.979,14.584,16,14.584z");
//            marker.setStyle(SimpleMarkerSymbol.STYLE_PATH);
            var renderer = new SimpleRenderer(marker);
            featureLayer.setRenderer(renderer);

            var template = new InfoTemplate();
            template.setTitle("Feature Details");
            featureLayer.setInfoTemplate(template);

            var drawToolbar = new Draw(map);
            var simpleMarkerSymbol = new SimpleMarkerSymbol();
            simpleMarkerSymbol.setStyle(SimpleMarkerSymbol.STYLE_CROSS);
            drawToolbar.setMarkerSymbol(simpleMarkerSymbol);


            featureLayer.on("mouse-over", function (evt) {
                console.log("mouse-over", evt);
                var nodes = dojoQuery(".tooltip");
                console.log("tooltip nodes", nodes)
                nodes[0].innerHTML = "click for info";
//                dojoQuery(".tooltip")[0].innerHTML = "click for info";
            });

            featureLayer.on("mouse-out", function (evt) {
                console.log("mouse-out", evt);
                dojoQuery(".tooltip")[0].innerHTML = "click to add a point";
            });


            var drawGraphic = true;

            map.on("click", function (evt) {
                console.log("map click", evt);
                if(evt.graphic){ // A graphic exist only if clicked on.
                    drawGraphic = false; // We set a flag to stop drawing the graphic and feature layer
                }
            });

            map.addLayers([featureLayer]);
            map.on("layers-add-result", initEditing);

            function initEditing(evt) {
                console.log("initEditing: ", evt);
                drawToolbar.activate(Draw.POINT);
                drawToolbar.on("draw-complete", function (evt) {
                    console.log("draw-complete", evt);
                    if (drawGraphic) {
                        var attributes = featureLayer.templates[0].prototype.attributes;
                        var graphic = new Graphic(evt.geometry, simpleMarkerSymbol, attributes);
                        featureLayer.applyEdits([graphic], null, null); // Does the CRUD on the FeatureLayer and then draws on the map
//                        map.graphics.add(graphic); // draws the symbol on the map, don't need to use if using // FeatureLayer.applyEdits()
                    }
                    else {
                        drawGraphic = true;
                    }
                });
            }; // end initEditing
        });
    </script>
</head>
<body>
<div>Drawing Features </div>

<div id="map"></div>

</body>
</html>

You can view the webpage here. Load the webpage into your browser at localhost and editor or IDE.

The map is created as in the previous tutorial. We dispense with using a basemap gallery and use only the Open Street Map (osm) basemap.

Feature Layer

The constructor for FeatureLayer needs a URL.

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html#featurelayer1

The URL should be to a single feature layer and not to the feature server. That is reason for the “0” in the URL.

The important options are the mode and outfield. Mode describes how the feature layer should be accessed. The common modes are when needed (MODE_ONDEMAND) or all at once (MODE_SNAPSHOT). Outfield is an array of the field values that should be downloaded for the graphics’ attributes. The “[*]” means all the fields in the table.

We want to specific how the features are rendered. Because the features are points, we only need to render them using SimpleMarkerSymbol.

https://developers.arcgis.com/javascript/3/jsapi/simplemarkersymbol-amd.html

Click on the “ArcGIS Symbol Playground” in the API specification for SimpleMarkerSymbol

https://developers.arcgis.com/javascript/3/samples/playground/index.html

Play with web app to see the different symbols that ArcGIS has. Be sure to look at SimpleMarkerSymbol and PictureMarkerSymbol. The default simple marker is a circle with diameter 16 pixels.

After creating the marker symbol we use it to make the renderer and set the feature layer’s renderer.

Next we make the infoWindow and set the feature layer’s to the new info window.

https://developers.arcgis.com/javascript/3/jsapi/infotemplate-amd.html

Drawing Tool

To draw new features on the feature layer we need a drawing tool. This is called Draw in ArcJIS.

https://developers.arcgis.com/javascript/3/jsapi/draw-amd.html

Funny that it is part of the toolbar, even though the webpage will not have a toolbar. Because the new features will be points, the drawing tool only needs a SimpleMarkerSymbol. The script use a different marker simple so that we differentiate the new symbol for the existing symbols during development. Finally the script set drawing tool marker symbol.

Tooltip

The tooltip is the message that appears adjacent to the cursor. It hints at what the user can do at the location. By default the drawToolbar’s tooltip will show “click to add point”. We would like the message at the tooltip to change to “click for info” when the cursor is over a feature. To do this we manipulate the Document Object Model (DOM).

Load the webpage into a browser and open the “inspect” development panel. Make sure that the “Elements” panel is visible. The html tags in the “Element” panel is the DOM. Look for the div with class=”tooltip”

<div class="tooltip" …>click to add point</div>

We want to change the text inside this div when the cursor is over a graphic/feature. FeatureLayer fire the “mouse-over” event when the mouse cursor enters a graphic.

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html#event-mouse-over

There is also a “mouse-out” event that is fired by the feature layer when the cursor exits the graphics. To change the tooltip message, we use some dojo query magic.

https://dojotoolkit.org/reference-guide/1.10/dojo/query.html

Dojo query searches the DOM and returns an array of nodes matching the filter. The filter or better called selector is the argument of the query. The selector specification is very similar to the CSS selector.

http://dojotoolkit.org/reference-guide/1.10/dojo/query.html#examples
http://dojotoolkit.org/reference-guide/1.10/dojo/query.html#standard-css2-selectors

The script specifies the selector “.tooltip”.

var nodes = dojoQuery(".tooltip");

Which means select all the nodes in the DOM that have class equal to “tooltip”. The array of nodes returned from dojo query is a NodeList, which comes with manipulation package.

https://dojotoolkit.org/reference-guide/1.10/dojo/NodeList-manipulate.html#dojo-nodelist-manipulate

We want to change the text in the div, so we use innerHTML method to change the message.

https://dojotoolkit.org/reference-guide/1.10/dojo/NodeList-manipulate.html#innerhtml

Note that dojo query and node list use chaining so that the operation could be more concisely written.

dojoQuery(".tooltip")[0].innerHTML = "click for info"

The feature layer on mouse-out event changes the message back to “click to add a point”.

Drawing logic

We do not want a mark drawn when the cursor is over a graphic, rather we want drawing to be enabled where there are not any graphics, so we use a flag to indicate when drawing is possible.

var drawGraphic = true;

We need to change the flag when the cursor is over a graphic, and we need to change it soon as the mouse the mouse has been clicked. The map click event is called before the feature layer click event, so we should use this event handler or callback to change the drawGraphic flag. But we need to know if a graphic has been clicked on. We use a trick. The map click event will have a graphic property if it is on a graphic of any feature layer. We set drawGraphic false if the map click has a graphic.

Drawing

Draw has a “draw-complete” event that fires when the drawing is complete.

https://developers.arcgis.com/javascript/3/jsapi/draw-amd.html#events

Drawing a point is complete after a single click, so we could use the map click callback to make the mark on the map and add the point to the feature layer, but not all graphics are a single click, so the tradition is to use the draw-complete event. We should stick to the tradition.

Note that we activate the drawToolba and defined the draw-complete callback only when the layer is ready for drawing, meaning after the layer has been added to the map, meaning in the layer-add callback, in this case the initEdit() function.

If we can draw, meaning drawGraphic is true, then we make a new graphics which requires attributes. We just use the prototype template for the feature layer to make the attributes for this tutorial. The next tutorial will show how to make specific attributes. To add the attributes to the feature layer we use FeatureLayer.ApplyEdits

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html#applyedits

FeatureLayer.applyEdits can add, update or delete an array of graphics in the feature layer. We want to “add” a point, so we put our array of graphics in the first argument

featureLayer.applyEdits([graphic], null, null);

We do not really need the two nulls in the argument list, but tradition is to have them so that it is easy to identify which operation is being performed.

FeatureLayer.applyEdits also has callbacks, but we don’t use them in this tutorial. As a side effect the applyEdits method also draws the marker on the map.

If we only wanted to add the marker to the screen and not store it in the feature layer GDB table we could use

map.graphics.add(graphic);

Attachments Tutorial

This tutorial demonstrates how users can store photos associated with a graphic in a feature layer and how they can view photos associated with a graphic.

In this tutorial you will learn about:

  • Editing Features
  • Adding Attachments
  • Modifying the Info Window

In the ArcGIS sample code, there are related examples, but none of them same show how to upload photos. These sample code were referenced during the development of this tutorial:

An Esri feature layer can store documents associated with graphics/features. The documents stored with graphics/features are called attachments. Esri handles them differently than graphic attributes. This is because more than one document can be associated with a graphic. Recall that feature layer has just a single table in the GDB, so attachments cannot be handled like attributes.

Esri has a widget, AttachmentEditor, to work with attachments.

https://developers.arcgis.com/javascript/3/jsapi/attachmenteditor-amd.html

But we will also want to edit the attributes. Esri has another widget, AttributeInspector, can show and edit the attributes associated with the graphics.

https://developers.arcgis.com/javascript/3/jsapi/attributeinspector-amd.html

AttributeInspector also has an AttachmentEditor. The constructor for the AttributeInspector has options for controlling how the attributes are edited and displayed in the info window, see layerInfos and fieldInfos options specifications.

https://developers.arcgis.com/javascript/3/jsapi/attributeinspector-amd.html#attributeinspector1

We are ready to look at the code.

The Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>Attach Editing</title>
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/dijit/themes/claro/claro.css">
    <link rel="stylesheet" href="https://js.arcgis.com/3.18/esri/css/esri.css">
    <style>
        html, body, #map {
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
    <script src="https://js.arcgis.com/3.18/"></script>
    <script>
        require(["esri/map",
                    "esri/layers/FeatureLayer",
                    "esri/symbols/SimpleMarkerSymbol",
                    "esri/Color",
                    "esri/graphic",
                    "esri/renderers/SimpleRenderer",
                    "esri/InfoTemplate",
                    "dojo/query",
                    "dojo/_base/array",
                    "esri/toolbars/draw",
                    "esri/dijit/AttributeInspector",
                    "esri/tasks/query",
                    "dojo/dom-construct",
                    "dojo/_base/array",
                    "esri/geometry/Extent",
                    "esri/geometry/screenUtils",

                    "dojo/NodeList-manipulate",
                    "dojo/NodeList-traverse",
                    "dojo/domReady!"
                ],
                function (Map,
                          FeatureLayer,
                          SimpleMarkerSymbol,
                          Color,
                          Graphic,
                          SimpleRenderer,
                          InfoTemplate,
                          dojoQuery,
                          arrayUtils,
                          Draw,
                          AttributeInspector,
                          EsriQuery,
                          domConstruct,
                          dojoArray,
                          Extent,
                          screenUtils
                ) {
                    var drawGraphic = true;
                    var map;
                    // Create the map with a basemap
                    map = new Map("map", {
                        basemap: "osm", //"topo",  //For full list of pre-defined basemaps, navigate to http://arcg.is/1JVo6Wd
                        center: [-88.448, 47.242], // longitude, latitude
                        zoom: 16,
                        logo: false
                    });

                    // create Feature Layer
                    var featureURL = "http://services2.arcgis.com/RPhrOu9XQzI31xTa/arcgis/rest/services/Robert_Point_Test/FeatureServer/0";
                    var featureLayer = new FeatureLayer(featureURL, {
                        mode: FeatureLayer.MODE_ONDEMAND,
                        outFields: ["*"]
                    });
                    var marker = new SimpleMarkerSymbol();
                    var renderer = new SimpleRenderer(marker);
                    featureLayer.setRenderer(renderer);

                    featureLayer.on("mouse-over", function (evt) {
                        var node = dojoQuery(".tooltip")[0];
                        node.innerHTML = "click for info";
                    });
                    featureLayer.on("mouse-out", function (evt) {
                        console.log("mouse-out", evt);
                        var node = dojoQuery(".tooltip")[0];
                        node.innerHTML = "click to add a point";
                    });

                    // create drawing toolbar for drawing a point
                    // need to activate the drawingToolbar before the first click
                    var drawToolbar = new Draw(map);
//                    drawToolbar.activate(Draw.POINT);

                    map.on("click", function (evt) {
                        console.log("map click event", evt);
                        if(evt.graphic){ // A graphic(eg. feature) exists in the event only if clicked on.
                            drawGraphic = false;
                            viewFeature(evt);
                            // Can not change drawGraphic to true here because of race conditions,
                            // on draw-complete callback will he called before the info window is made,
                            // need to change drawGraphic in the select features callback.
                        }
                    });

                    map.on("layers-add-result", initEdit);
                    map.addLayers([featureLayer]);

                    function initEdit(evt) {
                        console.log("initEdit evt: ", evt);
                        drawToolbar.activate(Draw.POINT);
                        drawToolbar.on("draw-complete", function (evt) {
                            console.log("draw-complete", evt);
                            if (drawGraphic) {
                                // Create the graphic
                                var prototypeAttributes = featureLayer.templates[0].prototype.attributes;
                                var newGraphic = new Graphic(evt.geometry, marker, prototypeAttributes);

                                // add the attributeInspector
                                var layerInfos = makeLayerInfos(true);
                                var attributeInspector = new AttributeInspector(
                                        {
                                            layerInfos: layerInfos
                                        },
                                        domConstruct.create("div")
                                );
                                attributeInspector.startup(); // Not really needed
                                console.log("attributeInspector", attributeInspector);
                                console.log("attributeInspector.domNode", attributeInspector.domNode);

                                // Add the graphics to the feature layer
                                featureLayer.applyEdits([newGraphic], null, null, function () {
                                    // This callback makes the info window
                                    map.infoWindow.setTitle("Make Feature");
                                    map.infoWindow.setContent(attributeInspector.domNode);
                                    map.infoWindow.resize(310, 600);

                                    var screenPoint = map.toScreen(newGraphic.geometry);
                                    map.infoWindow.show(screenPoint, map.getInfoWindowAnchor(screenPoint));
                                }); // Does the CRUD on the FeatureLayer and then draws on the map

                                // Attribucte Inspector Event Handlers
                                attributeInspector.on("attribute-change", function (evt) {
                                    console.log("attribute-change evt", evt);
                                    var feature = evt.feature;
                                    feature.attributes[evt.fieldName] = evt.fieldValue;
                                    feature.getLayer().applyEdits(null, [feature], null);
                                });

                                attributeInspector.on("delete", function (evt) {
                                    console.log("delete", evt);
                                    evt.feature.getLayer().applyEdits(null, null, [evt.feature]);
                                    map.infoWindow.hide();
                                })
                            } // end if drawGraphic
                        }); // end on draw-complete
                    } // end function initEditing(evt)

                    function viewFeature(evt) {
                        console.log("viewFeature evt:", evt);

                        // create attribute inspector that cannot edit
                        // NOTE: the Attribute Inspector needs to exist before the features are selected
                        // Other wise it does not know about the selection.
                        var layerInfos = makeLayerInfos(false);
                        var attributeInspector = new AttributeInspector(
                                {
                                    layerInfos: layerInfos
                                },
                                domConstruct.create("div")
                        );
                        attributeInspector.startup(); // Not really needed
                        console.log("attributeInspector.domNode", attributeInspector.domNode);

                        // Select features (ie Graphics) in the feature layer
                        // Geometry to query against
                        var markerSize = 20; // should be the approximate screen size of marker symbol size
                        var extent = new Extent(0, 0, markerSize, markerSize, null);
                        var screenExtent = extent.centerAt(evt.screenPoint);
                        var mapExtent = screenUtils.toMapGeometry(map.extent, map.width, map.height, screenExtent);

                        // construct the query
                        var query = new EsriQuery();
                        query.geometry = mapExtent;
                        query.spatialRelationship = EsriQuery.SPATIAL_REL_CONTAINS;

                        // make the spatial selection
                        featureLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW, function (features) {
                            // info window cannot be completed until after the selection
                            map.infoWindow.setTitle("View Feature");
                            map.infoWindow.setContent(attributeInspector.domNode);
                            map.infoWindow.resize(310, 600);
                            map.infoWindow.show(evt.screenPoint, map.getInfoWindowAnchor(evt.screenPoint));

                            // Fix the attachment editor by removing the attachment list and upload
                            var uploadFormNode = dojoQuery("form[dojoattachpoint=_uploadForm]");
                            uploadFormNode.remove("*");

                            dojoQuery("span[dojoattachpoint=_attachmentList]").remove("*");
                            dojoQuery("div[dojoattachpoint=_attachmentError]").siblings("br")
                                    .at(0)
                                    .after('<span dojoattachpoint="attachmentList" style="word-wrap: break-word;"></span>');
                            // Note: I removed the "_", just in case.

                            attributeInspector.first();

                            drawGraphic = true;
                        }); // end featureLayer.selectFeatures

                        attributeInspector.on("next", function (evt) {
                            console.log("next feature", evt);
                            // Remove the attachment list and then but the empty node back end
                            dojoQuery("span[dojoattachpoint=attachmentList]").remove("*");
                            dojoQuery("div[dojoattachpoint=_attachmentError]").siblings("br")
                                    .at(0)
                                    .after('<span dojoattachpoint="attachmentList" style="word-wrap: break-word;"></span>');

                            addAttachmentLinks(evt.feature.attributes.OBJECTID);
                        });

                        function addAttachmentLinks(objectId) {
                            featureLayer.queryAttachmentInfos(objectId, function (infos) {
//                                console.log("infos",infos);
                                // Need to put back in the <span><a href=" ?? " traget="_blank"> ??</a> <br> </span>
                                var attachmentListString ="";
                                dojoArray.forEach(infos,function(info){
                                    attachmentListString += '<span><a href="'+info.url+'" target="_blank">'+info.name+'</a><br></span>';
                                }); // end dojoArray.forEach
                                var attachmentNode = dojoQuery("span[dojoattachpoint=attachmentList]")[0];
                                domConstruct.place(attachmentListString, attachmentNode);
                            }); // end featureLayer.queryAttachmentInfos
                        }
                    } // end  function viewFeature(evt)

                    function makeLayerInfos(isEditable) {
//                        console.log("makeLayerInfos isEditable: ", isEditable);
                        var layerInfos = [
                            {
                                'featureLayer': featureLayer,
                                'showAttachments': true,
                                'isEditable': isEditable,
                                'showDeleteButton': isEditable,
                                'fieldInfos': [
                                    {'fieldName': 'shortint', 'isEditable': isEditable, 'label': 'Short Int:'},
                                    {'fieldName': 'longint', 'isEditable': isEditable,  'label': 'Long Int:'},
                                    {'fieldName': 'text50', 'isEditable': isEditable, 'label': 'Text 50:'},
                                    {'fieldName': 'OBJECTID', 'isEditable': false, 'label': 'Object Id:'}
                                ]
                            }
                        ];
                        return layerInfos;
                    } // end function makeLayerInfos(isEditable)
                }); // end require-function
    </script>
</head>
<body>
<div> View or Make Features </div>
<div id="map"></div>

</body>
</html>

You can view the webpage here. Load the webpage into your browser at localhost and editor or IDE.

We create the map and add the feature layer just as the previous tutorial. We also control the tooltip message and editing with drawGraphic flag as we did in the previous tutorial.

Editing Attributes

Drawing and editing the attributes occurs in the callback for map “layers-add-result” event, initEdit function. The “layers-add-result” event is slight different than the “layer-add” event. The Map.addLayers(…) simultaneously adds an array of feature layers to the map and the layers-add-result does not fire until all the layers have been added. This is the standard design pattern to use when editing multiple feature layers simultaneously. This tutorial has a single feature layer, so we could have used the layer-add event callback, but we should stick with the standard design pattern.

After checking for drawGraphic in the initEdit function, the code creates the graphics as in the previous tutorial. A function used to make the layersInfos option for the attribute inspector constructor, makeLayerInfos. You can find makeLayerInfos function at the bottom of the script.

The attribute inspector needs a “div” to be added to the DOM. The dojo/dom-construct package is used to create the div for the attribute inspector.

https://dojotoolkit.org/reference-guide/1.10/dojo/dom-construct.html#create

We are supposed to start up the attribute inspector, but I found it is not necessary. We apply the edits to the feature layer as in the previous tutorial, but we use the callback for applyEdits to make the infoWindow. The attribute inspector node is attached to the infoWindow by:

map.infoWindow.setContent(attributeInspector.domNode);

The API description does specify the domNode property, but the code samples illustrate it use.

We will locate the infoWindow near the click point. The newGraphic has the geometry property which gives the location of newGraphics in map coordinates. We use the map method, toScreen, to get the location in screen coordinates, pixels.

The API specification does not specify much for getInfoWindowAnchor

https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#getinfowindowanchor

But it is always used when locating and showing the infoWindow.

Attribute editing is handled by the callback for attribute inspector “attribute-change” event. The event is fired whenever a field has changed and the field uses the focus, for example when the user clicks on new field after entering text in the previous field. We need to set the feature/graphic attribute value before calling applyEdits

feature.attributes[evt.fieldName] = evt.fieldValue;
feature.getLayer().applyEdits(null, [feature], null);

Note that the feature attribute is updated by using the second argument of applyEdits.

Viewing Attributes

The function viewFeatures is called when clicking on a map graphic. The function makes another attribute inspector but with isEditable set to false by the makeLayerInfos function.

When zoomed out the graphic markers can be stacked, but the click event only returns a single graphic not all the stacked graphics. To collect all the stacked graphic features that were clicked, we have to make a geospatial query. We first make a screen extent that is approximate size of the marker symbol centered at the screen click point.

var markerSize = 20; // should be the approximate screen size of marker symbol size
var extent = new Extent(0, 0, markerSize, markerSize, null);
var screenExtent = extent.centerAt(evt.screenPoint);
var mapExtent = screenUtils.toMapGeometry(map.extent, map.width, map.height, screenExtent);

See the API description for Extent.

https://developers.arcgis.com/javascript/3/jsapi/extent-amd.html

Then we transform the screen extent to a map extent using screenUtilits.

https://developers.arcgis.com/javascript/3/jsapi/esri.geometry.screenutils-amd.html

The mapExtent now describes a geospatial area and can be used in a geospatial query. We first setup the query.

var query = new EsriQuery();
query.geometry = mapExtent;
query.spatialRelationship = EsriQuery.SPATIAL_REL_CONTAINS;

The geometry query property can be any geometry, in this case it is extent of the marker in map coordinates. The spatialRealtionship query property sets the type of query

https://developers.arcgis.com/javascript/3/jsapi/query-amd.html#constants
https://developers.arcgis.com/javascript/3/jsapi/query-amd.html#properties

The relationship between the query geometry and the geometries of the entries in the GDB table can have different relationship. For example the query geometry can cross (SPATIAL_REL_CROSSES) or intersects (SPATIAL_REL_INTERSECTS) the entry geometries. Esri documentation about spatial relationships is concise.

http://desktop.arcgis.com/en/arcmap/10.3/manage-data/using-sql-with-gdbs/relational-functions-for-st-geometry.htm

Note that ST_*** are the functions used by the query when called using SPATIAL_R_*** spatial relationship constant, for example if the spatialRelationship = EsriQuery.SPATIAL_REL_CONTAINS then the ST_Contains function will be used in the query. This is a very large topic. The topic is best learned in a series of tutorial. A good tutorial is at Boundless.

http://workshops.boundlessgeo.com/postgis-intro/

Note that if you install Boundless OpenGeo Suite (as in the third tutorial) it uses port 8080 of your localhost and does not release the port. If you run a Grails app on your localhost after installing Boundless OpenGeo Suite you will get an error.

ERROR org.apache.coyote.http11.Http11NioProtocol - Failed to start end point associated with ProtocolHandler ["http-nio-8080"]
java.net.BindException: Address already in use: bind

I could only resolve the error by uninstalling OpenGeo Suite. Recall that the browser cashes webpages so that just check localhost:8080 is not sufficient after uninstalling OpenGeo Suite. You must load localhost:8080 with a new webpage.

We want to select all the graphics/features within mapExtent, so the spatial relation to use SPATIAL_REL_CONTIANTS. After setting up the spatial query, we use FeatureLayer.selectFeature method to make the query and select all the features/graphics within mapExtent.

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html#selectfeatures

We use the callback function of selectFeatures to make the info window for the attribute inspector.

Although we specified that the layer should not be editable in this attribute inspector it does not affect the attachment editor, meaning the “upload file” and delete attachment red X appear in the info window. You can check this, by commenting out in the selectFeatures callback the following lines.

var uploadFormNode = dojoQuery("form[dojoattachpoint=_uploadForm]");
uploadFormNode.remove("*");
dojoQuery("span[dojoattachpoint=_attachmentList]").remove("*");
dojoQuery("div[dojoattachpoint=_attachmentError]").siblings("br")
 .at(0)
 .after('<span dojoattachpoint="attachmentList" style="word-wrap: break-word;"></span>');
// Note: I removed the "_", just in case.
attributeInspector.first();

Load the webpage into the browser and click on the graphic, circle, in Agassiz Park in Calumet. This graphic has an attached photo. In the attachment panel there is a link to the photo and a red X. Do not click on the red X; this will delete the photo. Below is the “Choose File” button to upload another file. You can an upload file if you wish. We do not want these operations when viewing a graphics.

The commented out code is dojo query magic to remove the upload form and the list of attachments. See the dojo query, node list manipulation and transverse packages API for details.

https://dojotoolkit.org/reference-guide/1.10/dojo/query.html
https://dojotoolkit.org/reference-guide/1.10/dojo/NodeList-manipulate.html#dojo-nodelist-manipulate
https://dojotoolkit.org/reference-guide/1.10/dojo/NodeList-traverse.html#dojo-nodelist-traverse

With the above code commented out, you can view the original attribute inspector html code using the developer tools to inspect the code printed in the console by the log:

console.log("attributeInspector.domNode", attributeInspector.domNode);

You will have expand many tags to find the div tag containing the attachments. If you do not comment out the dojo magic lines then by the time you look at the html code in the console it will already have been modified by dojo query magic.

For the same reason, we have to remove the entire attachmentList from the DOM. Originally, I tried to extract the anchor tags from the list to reuse, but the dojo query for the anchors always returned an empty list. This is because JavaScript is asynchronous and the attachment list had not been populated. We replace the span tag with class dojoattachpoint=_attachmentList with a slightly different class, so that the attachment editor code cannot attach the list of anchor.

You can uncomment the dojo query magic lines now.

We call attribute inspector first() method so that we know what feature is shown in the infoWindow.

Before we leave the call back for selectFeatures note that we set the drawGraphic flag to true. This is the latest time that we can set drawGraphic in the code. The callback for map click finishes before the viewFeature method finishes, so we cannot set drawGraphic in the map click callback.

The attribute inspector “next” event fires whenever the user clicks the arrow to look at the next feature to view in the stack.

https://developers.arcgis.com/javascript/3/jsapi/attributeinspector-amd.html#event-next

We have to remove the attachment list again and add the span back into the attachment editor DOM.

The method addAttachmentLinks uses the object id to query the attachment info.

https://developers.arcgis.com/javascript/3/jsapi/featurelayer-amd.html#queryattachmentinfos

In the callback, the code constructs the list of attachment links and then locates it into the DOM.

var attachmentListString ="";
dojoArray.forEach(infos,function(info){
     attachmentListString += '<span><a href="'+info.url+'" target="_blank">'+info.name+'</a><br></span>';
}); // end dojoArray.forEach
var attachmentNode = dojoQuery("span[dojoattachpoint=attachmentList]")[0];
domConstruct.place(attachmentListString, attachmentNode);

See the API description for the array utility and DOM construct

http://dojotoolkit.org/reference-guide/1.10/dojo/_base/array.html
https://dojotoolkit.org/reference-guide/1.10/dojo/dom-construct.html#place

Evaluate the Program

Play with the code. It works, but I not happy with it. I think that it would have been better not to use the attribute inspector. The code is slow to load the attachment list. This is because it calls the database twice to get the attachment list. I think making our own content for the info window would be better. See the code sample:

https://developers.arcgis.com/javascript/3/jssamples/widget_extendInfowindow.html
https://developers.arcgis.com/javascript/3/jssamples/widget_formatInfoWindow.html

Also the edit is not intuitive. There is not a submit or save button. This code sample shows how to add a save button.

https://developers.arcgis.com/javascript/3/jssamples/ed_attribute_inspector.html

You can even put the infoWindow in a side panel

https://developers.arcgis.com/javascript/3/jssamples/popup_sidepanel.html

For extra credit you can change the delete button text to “Cancel” or make your own content for the info window.

Feature Class and Creating a Feature Service

You will need a feature server for your app. Although Don or his student will make the feature service for you, you need to specify the feature service that your app will need. First, we should review terminology.

Feature Terminology

ESRI defines a “feature” as a representation of an object on the a map.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature

A “feature class” is defined as a set of features all the same geometry type and attributes.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature%20class

We have already discussed geometries. There are basically 4 types: extent, point, multi-point, point, poloylines, and polygons.

https://developers.arcgis.com/javascript/3/jsapi/geometry-amd.html

There is also a “feature dataset”, which is a collection of feature classes. They do not have to have the same geometric type, but they are in the same geographic area. Generally they have an association such as “potholes” feature class (with geometric type point) and “sewer lines” feature class (with geometric type lines) making up a “drainage” feature dataset. I doubt you’ll need a feature dataset, so I’ll not write more.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature%20dataset

A “feature service” is basic a feature class that is available on the web.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature%20service

The “feature server” is server serving the feature over the web.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature%20server

There is also the “feature layer” which you have already seen is the data from the feature class rendered on a map.

http://support.esri.com/sitecore/content/support/Home/other-resources/gis-dictionary/term/feature%20layer

That is a lot of features. This diagram can help.

feature < feature class < feature dataset < feature service < feature server --> feature layer

In other words feature class is a collection of features. Feature dataset is a collection of feature class. Both feature class and dataset can be made into services which the feature server can provide to clients to display on their maps as feature layers.

Creating the feature service is long process, involving making the feature class using ArcMap or ArcDesktop, defining the feature service and publishing the feature service. The key step is making the feature class.

Defining the Feature Class

Feature Class Type

http://webhelp.esri.com/arcgisdesktop/9.2/index.cfm?TopicName=Feature_class_basics

The above reference is rather involved. The point is the first step in making the feature class is deciding on the feature class. There are 7 feature class types, but generally the feature class type is either Points, Multipoints, Lines, or Polygons. The feature class types of Annotation, Dimensions, and Multipatches are advance types for rendering maps or working in 3D.

The historical photo app in this tutorial would use a Point feature class type.

Feature Attributes

The next step is to specify feature attributes. These are the fields in the geodatabase table. The data type needs to specified. The data types are:

  • BLOB 
  • DATE 
  • DOUBLE 
  • FLOAT 
  • GUID – Global Universital ID. You always get this field.
  • LONG INTEGER
  • OBJECT ID – this is for the primaray key that is auto incremented. You always get this field.
  • SHORT INTEGER
  • TEXT – number of characters specified. Default is 50 characters.

http://webhelp.esri.com/arcgisdesktop/9.2/index.cfm?TopicName=Geodatabase%20field%20data%20types

Domains

Any data type can have restrictions. The are specified by the domain.

http://webhelp.esri.com/arcgisdesktop/9.2/index.cfm?TopicName=An_overview_of_attribute_domains

For example, integers can have a range domain that restricts allowable values from 1 to 10. A text data type can have a coded domain that restricts it to specific values, for example “cat”, “dog” and “mouse”.  Associating a domain to a field data type restricts the values that can be put into that field. If you have a categorical variable in your app, you will probably want to data type of the field in the geodatabase to be associated with a coded domain.

Specifying the Feature Class

Because ArcMap interacts well with Excel, to specify your feature class requirements to Don, you should create an excel file. The excel should have a single row with the field names. Then you specify data type by right click on the column of the field and selecting “Format Cell”. In the window that opens, select the “Number” tab. In the Number tab, select the data type that you want:

  • Number
  • Date
  • Time
  • Scientific
  • Text

If you desire that a field should have a domain, then specify the domain in the body of the email.