How To Create a Polygon JS API 4.9

4120
9
Jump to solution
11-05-2018 11:19 PM
SimonWebster
Occasional Contributor

I'm attempting to follow the sample code provided here to allow users to create polygons using FeatureLayer.applyEdits(). The example is explicitly set up to capture point information, and is thus not quite capable "out of the box" of capturing polygon edits/creates.

To date I've tried: 

  • Changing the layer to a polygon layer hosted on AGOL with anonymous editing enabled (sandbox for proof of concept).
  • Changing the geometry type on line 291 to polygon
  •    - This generates an uncaught reference error that polygon is undefined, though it seems to be handled in MapView.js. 

I rather like the look with the box / feature info collector, etc, and could probably repurpose it, if only I could figure out the drawing component. 

Any bright ideas on what else I might need to change / modify to be able to change this example to digitise polygons?

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Esteemed Contributor

Simon,

   You issue in the code is that you were not passing any attributes (even if it is a empty object). See line 8 for the fix.

        // called when sketchViewModel's create-complete event is fired.
        function addGraphic(event) {
          // Create a new graphic and set its geometry to
          // `create-complete` event geometry.
          const graphic = new Graphic({
            geometry: event.geometry,
            symbol: sketchViewModel.graphic.symbol,
            attributes: {}
          });
...

Also you have a function in the applyEdits method called selectFeatures that does not exist in your code (line 10, I commented it out):

      // ***********************************************************
      // Call FeatureLayer.applyEdits() with specified params.
      // ***********************************************************
      function applyEdits(params) {
        featureLayer.applyEdits(params).then(function(editsResult) {
          // Get the objectId of the newly added feature.
          // Call selectFeature function to highlight the new feature.
          if (editsResult.addFeatureResults.length > 0) {
            const objectId = editsResult.addFeatureResults[0].objectId;
            //selectFeature(objectId);
          }
        })

View solution in original post

0 Kudos
9 Replies
RobertScheitlin__GISP
MVP Esteemed Contributor

Simon,

   You would have to do a lot more then just change a few lines to make that sample work with a Polygon.

You need to be able to digitize the polygon geometry first. You could try combining this draw geomtries sample with that sample.

https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=sketch-geometr... 

0 Kudos
SimonWebster
Occasional Contributor

Thanks Robert

I may very well be able to chain sketch and create features. I'll see how it goes. 

0 Kudos
SimonWebster
Occasional Contributor

Robert Scheitlin, GISP

**NOTE** When using the sample code, you should always work with your own services, not someone elses - even if they left a service URL in the code. People have taken the time to help you by posting, updating, and replying to these threads, so don't be a bad colleague by adding to others layers. **NOTE** This code is literally drop in an go. Make a feature layer on AGOL and drop it in to the two locations where the URL is referenced, and go. 

I've taken your suggestion to work with sketch-geometries first, and then try to apply edits. I've made some good progress with submitting to the server but hit a snag that I'm not sure how to resolve. 


The main issue is that the parameters being passed to the FeatureServer/0/applyEdits function seem to be invalid. Specifically I'm somehow butchering the 'adds' component, and I'm not sure what the object reference is referring to. Full code is included at the end if you're inclined to offer some advice, as is a publically editable / empty / temp layer. Thanks.

Undral Batsukh‌, this might interest you as you had also replied. 

Console output:

[ applyEdits ] FAILURE:  undefined request:server Cannot perform operation. Invalid operation parameters.

error =  e {name: "request:server", message: "Cannot perform operation. Invalid operation parameters.", details: {…}}
details:
getHeader: ƒ ()
arguments: (...)
caller: (...)
length: 1
name: "bound k"
__proto__: ƒ ()
[[TargetFunction]]: ƒ k(a)
[[BoundThis]]: Object
[[BoundArgs]]: Array(0)
httpStatus: 400
messageCode: undefined
messages: Array(2)
0: "'adds' parameter is invalid"
1: "Object reference not set to an instance of an object."
length: 2‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The changes I've made to get to this point are (in full further below):
Take this example: ArcGIS API for JavaScript Sandbox 

And this example: ArcGIS API for JavaScript Sandbox 

Using the sketch example - I've added a reference to the addFeatures function. :

        // called when sketchViewModel's create-complete event is fired.
function addGraphic(event) {
// Create a new graphic and set its geometry to
// `create-complete` event geometry.
const graphic = new Graphic({
geometry: event.geometry,
symbol: sketchViewModel.graphic.symbol
});

graphicsLayer.add(graphic);

const edits = { //Fire the addFeatures function using the completed graphic
addFeatures: [graphic]
};
applyEdits(edits);

console.log(graphic);
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Added the applyEdits function, with error handling:

        setUpClickHandler();

// ***********************************************************
// Call FeatureLayer.applyEdits() with specified params.
// ***********************************************************
function applyEdits(params) {
featureLayer.applyEdits(params).then(function(editsResult) {
// Get the objectId of the newly added feature.
// Call selectFeature function to highlight the new feature.
if (editsResult.addFeatureResults.length > 0) {
const objectId = editsResult.addFeatureResults[0].objectId;
selectFeature(objectId);
}
})
.catch(function(error) {
console.log("===============================================");
console.error("[ applyEdits ] FAILURE: ", error.code, error.name,
error.message);
console.log("error = ", error);
});
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Updated the require, function, let, and layer code at the top of the <script> section.
The layer listed is publically open for testing/assistance at the moment (non prod, no data):

    require([
"esri/views/MapView",
"esri/Map",
"esri/widgets/Sketch/SketchViewModel",
"esri/Graphic",
"esri/layers/GraphicsLayer",
"esri/layers/FeatureLayer",
"esri/widgets/Expand",
"esri/widgets/FeatureForm"
], function (
MapView, Map,
SketchViewModel, Graphic, GraphicsLayer, FeatureLayer, Expand, FeatureForm
) {

let editFeature, editGraphic, highlight, featureForm, editArea, attributeEditing, updateInstructionDiv;

const featureLayer = new FeatureLayer({
url: "https://services1.arcgis.com/RanDomKeySample/arcgis/rest/services/ServiceTitle/FeatureServer/0",
outFields: ["*"],
popupEnabled: false,
id: "HotspotsLayer"
});

// GraphicsLayer to hold graphics created via sketch view model
const graphicsLayer = new GraphicsLayer({
id: "tempGraphics"
});‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The complete page code:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Sketch temporary geometries - 4.9</title>

<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
<script src="https://js.arcgis.com/4.9/"></script>

<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
font-family: verdana;
}

#topbar {
background: #fff;
position: absolute;
top: 15px;
right: 15px;
padding: 10px;
}

.action-button {
font-size: 16px;
background-color: transparent;
border: 1px solid #D3D3D3;
color: #6e6e6e;
height: 32px;
width: 32px;
text-align: center;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.3);
}

.action-button:hover,
.action-button:focus {
background: #0079c1;
color: #e4e4e4;
}

.active {
background: #0079c1;
color: #e4e4e4;
}

</style>

<script>
require([
"esri/views/MapView",
"esri/Map",
"esri/widgets/Sketch/SketchViewModel",
"esri/Graphic",
"esri/layers/GraphicsLayer",
"esri/layers/FeatureLayer",
"esri/widgets/Expand",
"esri/widgets/FeatureForm"
], function (
MapView, Map,
SketchViewModel, Graphic, GraphicsLayer, FeatureLayer, Expand, FeatureForm
) {

let editFeature, editGraphic, highlight, featureForm, editArea, attributeEditing, updateInstructionDiv;

const featureLayer = new FeatureLayer({
url: "https://services1.arcgis.com/RaNdomKeySample/arcgis/rest/services/ServiceTitle/FeatureServer/0",
outFields: ["*"],
popupEnabled: false,
id: "HotspotsLayer"
});

// GraphicsLayer to hold graphics created via sketch view model
const graphicsLayer = new GraphicsLayer({
id: "tempGraphics"
});

const map = new Map({
basemap: "gray",
layers: [graphicsLayer]
});

const view = new MapView({
container: "viewDiv",
map: map,
zoom: 3
});

const pointSymbol = {
type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
style: "square",
color: "#8A2BE2",
size: "16px",
outline: { // autocasts as new SimpleLineSymbol()
color: [255, 255, 255],
width: 3
}
};

const polylineSymbol = {
type: "simple-line", // autocasts as new SimpleLineSymbol()
color: "#8A2BE2",
width: "4",
style: "dash"
};

const polygonSymbol = {
type: "simple-fill", // autocasts as new SimpleFillSymbol()
color: "rgba(138,43,226, 0.8)",
style: "solid",
outline: {
color: "white",
width: 1
}
};

view.when(function () {
// create a new sketch view model
const sketchViewModel = new SketchViewModel({
view,
layer: graphicsLayer,
pointSymbol,
polylineSymbol,
polygonSymbol
});

setUpClickHandler();

// ***********************************************************
// Call FeatureLayer.applyEdits() with specified params.
// ***********************************************************
function applyEdits(params) {
featureLayer.applyEdits(params).then(function(editsResult) {
// Get the objectId of the newly added feature.
// Call selectFeature function to highlight the new feature.
if (editsResult.addFeatureResults.length > 0) {
const objectId = editsResult.addFeatureResults[0].objectId;
selectFeature(objectId);
}
})
.catch(function(error) {
console.log("===============================================");
console.error("[ applyEdits ] FAILURE: ", error.code, error.name,
error.message);
console.log("error = ", error);
});
}

// Listen to create-complete event to add a newly created graphic to view
sketchViewModel.on("create-complete", addGraphic);

// Listen the sketchViewModel's update-complete and update-cancel events
sketchViewModel.on("update-complete", updateGraphic);
sketchViewModel.on("update-cancel", updateGraphic);

// called when sketchViewModel's create-complete event is fired.
function addGraphic(event) {
// Create a new graphic and set its geometry to
// `create-complete` event geometry.
const graphic = new Graphic({
geometry: event.geometry,
symbol: sketchViewModel.graphic.symbol
});

graphicsLayer.add(graphic);

const edits = { //Fire the addFeatures function using the completed graphic
addFeatures: [graphic]
};
applyEdits(edits);

console.log(graphic);
}

// Runs when sketchViewModel's update-complete or update-cancel
// events are fired.
function updateGraphic(event) {
// Create a new graphic and set its geometry event.geometry
var graphic = new Graphic({
geometry: event.geometry,
symbol: editGraphic.symbol
});
graphicsLayer.add(graphic);

// set the editGraphic to null update is complete or cancelled.
editGraphic = null;
}

// set up logic to handle geometry update and reflect the update on "graphicsLayer"
function setUpClickHandler() {
view.on("click", function (event) {
view.hitTest(event).then(function (response) {
var results = response.results;
if (results.length > 0) {
for (var i = 0; i < results.length; i++) {
// Check if we're already editing a graphic
if (!editGraphic && results[i].graphic.layer.id === "tempGraphics") {
// Save a reference to the graphic we intend to update
editGraphic = results[i].graphic;

// Remove the graphic from the GraphicsLayer
// Sketch will handle displaying the graphic while being updated
graphicsLayer.remove(editGraphic);
sketchViewModel.update(editGraphic);
break;
}
}
}
});
});
}

// activate the sketch to create a point
var drawPointButton = document.getElementById("pointButton");
drawPointButton.onclick = function () {
// set the sketch to create a point geometry
sketchViewModel.create("point");
setActiveButton(this);
};

// activate the sketch to create a polyline
var drawLineButton = document.getElementById("polylineButton");
drawLineButton.onclick = function () {
// set the sketch to create a polyline geometry
sketchViewModel.create("polyline");
setActiveButton(this);
};

// activate the sketch to create a polygon
var drawPolygonButton = document.getElementById("polygonButton");
drawPolygonButton.onclick = function () {
// set the sketch to create a polygon geometry
sketchViewModel.create("polygon");
setActiveButton(this);
};

// activate the sketch to create a rectangle
var drawRectangleButton = document.getElementById(
"rectangleButton");
drawRectangleButton.onclick = function () {
// set the sketch to create a polygon geometry
sketchViewModel.create("rectangle");
setActiveButton(this);
};

// activate the sketch to create a circle
var drawCircleButton = document.getElementById("circleButton");
drawCircleButton.onclick = function () {
// set the sketch to create a polygon geometry
sketchViewModel.create("circle");
setActiveButton(this);
};

// reset button
document.getElementById("resetBtn").onclick = function () {
sketchViewModel.reset();
graphicsLayer.removeAll();
setActiveButton();
};

function setActiveButton(selectedButton) {
// focus the view to activate keyboard shortcuts for sketching
view.focus();
var elements = document.getElementsByClassName("active");
for (var i = 0; i < elements.length; i++) {
elements[i].classList.remove("active");
}
if (selectedButton) {
selectedButton.classList.add("active");
}
}
});
});

</script>
</head>

<body>
<div id="viewDiv">
<div id="topbar">
<button class="action-button esri-icon-blank-map-pin" id="pointButton"
type="button" title="Draw point"></button>
<button class="action-button esri-icon-polyline" id="polylineButton" type="button"
title="Draw polyline"></button>
<button class="action-button esri-icon-polygon" id="polygonButton" type="button"
title="Draw polygon"></button>
<button class="action-button esri-icon-checkbox-unchecked" id="rectangleButton"
type="button" title="Draw rectangle"></button>
<button class="action-button esri-icon-radio-unchecked" id="circleButton"
type="button" title="Draw circle"></button>
<button class="action-button esri-icon-trash" id="resetBtn" type="button"
title="Clear graphics"></button>
</div>
</div>
</body>

</html>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
RobertScheitlin__GISP
MVP Esteemed Contributor

Simon,

   You issue in the code is that you were not passing any attributes (even if it is a empty object). See line 8 for the fix.

        // called when sketchViewModel's create-complete event is fired.
        function addGraphic(event) {
          // Create a new graphic and set its geometry to
          // `create-complete` event geometry.
          const graphic = new Graphic({
            geometry: event.geometry,
            symbol: sketchViewModel.graphic.symbol,
            attributes: {}
          });
...

Also you have a function in the applyEdits method called selectFeatures that does not exist in your code (line 10, I commented it out):

      // ***********************************************************
      // Call FeatureLayer.applyEdits() with specified params.
      // ***********************************************************
      function applyEdits(params) {
        featureLayer.applyEdits(params).then(function(editsResult) {
          // Get the objectId of the newly added feature.
          // Call selectFeature function to highlight the new feature.
          if (editsResult.addFeatureResults.length > 0) {
            const objectId = editsResult.addFeatureResults[0].objectId;
            //selectFeature(objectId);
          }
        })

View solution in original post

0 Kudos
SimonWebster
Occasional Contributor

Robert,

That was just what I needed to kick it over the line – thank you!

Simon

0 Kudos
RobertScheitlin__GISP
MVP Esteemed Contributor

Don't forget to mark this question as answered by clicking on the "Mark Correct" link on the reply that answered your question.

0 Kudos
SimonWebster
Occasional Contributor

Marked as correct, thank you. 

0 Kudos
UndralBatsukh
Esri Regular Contributor

What Robert suggested will work. However, SketchViewModel uses GraphicsLayer and GraphicsLayer generalizes  geometries. If you are storing these polygon geometries in a FeatureLayer, you have to make sure that these geometries are not generalized (if it is important to you). You may have to use FeatureLayer.queryFeatures to get the geometries from the server and have your end user interact with geometries returned from the server.

0 Kudos
SimonWebster
Occasional Contributor

Thanks, though I'm not entirely sure how this will manifest as an issue for me yet. Good to know, however. 

0 Kudos