|
POST
|
There doesn't appear to be a simple way to do this, but the following function would do the job in theory. Note, due to the nature of hitTest, it can't just return a Boolean, but instead has to return a Promise. function checkCluster(view, graphic) {
return new Promise(function(resolve, reject) {
view.hitTest(view.toScreen(graphic.geometry), {include:[graphic.layer]}).then(function(hitTestResult) {
var result = ((hitTestResult.results.length === 0) || (hitTestResult.results[0].isAggregate === true));
resolve({graphic:graphic, isCluster:result});
}, reject);
});
} However, it doesn't work as expected in practice. The reason is because in cases where a graphic that is part of a cluster doesn't intersect the cluster graphic, hitTest will still return the graphic from the layer, even though it's not visible in the view. I don't think hitTest is supposed to work this way. The documentation is somewhat ambiguous, but my expectation is that it's supposed to only return objects that are visible in the view. After all, why would a hit test return something that's not rendered in the view? It doesn't seem like you should be able to"hit" something that's not there. @UndralBatsukh could you please weigh in here? Here is the adjusted code for testing that you can paste into the Sandbox for the "Intro to clustering" sample. If you click somewhere on the map, the dev tools console will output results, indicating if the features whose coordinates (but not symbology!) intersecting the view are part of a cluster or not. I highly recommend panning to a region where there's only a few, for clarity of results. <html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>Intro to clustering | Sample | ArcGIS Maps SDK for JavaScript 4.30</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
background: rgba(50,50,50);
}
#infoDiv {
padding: 10px;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/dark/main.css" />
<script src="https://js.arcgis.com/4.30/"></script>
<script>
require([
"esri/Map",
"esri/layers/FeatureLayer",
"esri/layers/GeoJSONLayer",
"esri/views/MapView",
"esri/widgets/Legend",
"esri/widgets/Expand",
"esri/widgets/Home"
], (Map, FeatureLayer, GeoJSONLayer, MapView, Legend, Expand, Home) => {
// Configures clustering on the layer. A cluster radius
// of 100px indicates an area comprising screen space 100px
// in length from the center of the cluster
const clusterConfig = {
type: "cluster",
clusterRadius: "100px",
// {cluster_count} is an aggregate field containing
// the number of features comprised by the cluster
popupTemplate: {
title: "Cluster summary",
content: "This cluster represents {cluster_count} earthquakes.",
fieldInfos: [{
fieldName: "cluster_count",
format: {
places: 0,
digitSeparator: true
}
}]
},
clusterMinSize: "24px",
clusterMaxSize: "60px",
labelingInfo: [{
deconflictionStrategy: "none",
labelExpressionInfo: {
expression: "Text($feature.cluster_count, '#,###')"
},
symbol: {
type: "text",
color: "#004a5d",
font: {
weight: "bold",
family: "Noto Sans",
size: "12px"
}
},
labelPlacement: "center-center",
}]
};
const layer = new GeoJSONLayer({
title: "Earthquakes from the last month",
url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.geojson",
copyright: "USGS Earthquakes",
featureReduction: clusterConfig,
// popupTemplates can still be viewed on
// individual features
popupTemplate: {
title: "Magnitude {mag} {type}",
content: "Magnitude {mag} {type} hit {place} on {time}",
fieldInfos: [
{
fieldName: "time",
format: {
dateFormat: "short-date-short-time"
}
}
]
},
renderer: {
type: "simple",
field: "mag",
symbol: {
type: "simple-marker",
size: 4,
color: "#69dcff",
outline: {
color: "rgba(0, 139, 174, 0.5)",
width: 5
}
}
}
});
// background layer for geographic context
// projected to Alaska Polar Stereographic
const baseLayer = new FeatureLayer({
portalItem: {
id: "2b93b06dc0dc4e809d3c8db5cb96ba69"
},
legendEnabled: false,
popupEnabled: false,
renderer: {
type: "simple",
symbol: {
type: "simple-fill",
color: [65, 65, 65, 1],
outline: {
color: [50, 50, 50, 0.75],
width: 0.5
}
}
},
spatialReference: {
wkid: 5936
}
});
const map = new Map({
layers: [baseLayer, layer]
});
const view = new MapView({
container: "viewDiv",
extent: {
spatialReference: {
wkid: 5936
},
xmin: 1270382,
ymin: -1729511,
xmax: 2461436,
ymax: -953893
},
spatialReference: {
// WGS_1984_EPSG_Alaska_Polar_Stereographic
wkid: 5936
},
constraints: {
minScale: 15469455
},
map: map
});
view.ui.add(new Home({
view: view
}), "top-left");
const legend = new Legend({
view: view,
container: "legendDiv"
});
const infoDiv = document.getElementById("infoDiv");
view.ui.add(new Expand({
view: view,
content: infoDiv,
expandIcon: "list-bullet",
expanded: false
}), "top-left");
const toggleButton = document.getElementById("cluster");
// To turn off clustering on a layer, set the
// featureReduction property to null
toggleButton.addEventListener("click", () => {
let fr = layer.featureReduction;
layer.featureReduction = fr && fr.type === "cluster" ? null : clusterConfig;
toggleButton.innerText = toggleButton.innerText === "Enable Clustering" ? "Disable Clustering" : "Enable Clustering";
});
function checkCluster(view, graphic) {
return new Promise(function(resolve, reject) {
view.hitTest(view.toScreen(graphic.geometry), {include:[graphic.layer]}).then(function(hitTestResult) {
var result = ((hitTestResult.results.length === 0) || (hitTestResult.results[0].isAggregate === true));
resolve({graphic:graphic, isCluster:result});
}, reject);
});
}
view.on("click", function(evt) {
view.whenLayerView(layer).then(function(layerView) {
var query = layerView.createQuery();
query.geometry = view.extent;
layerView.queryFeatures(query).then(function(featureSet) {
featureSet.features.forEach(function(feature, index) {
checkCluster(view, feature).then(function(result) {
console.info(result.graphic.getObjectId().toString() + ": " + result.isCluster.toString());
});
});
});
});
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="infoDiv" class="esri-widget">
<button id="cluster" class="esri-button">Disable Clustering</button>
<div id="legendDiv"></div>
</div>
</body>
</html> In this example below, we see there's a cluster of 10 features, and a standalone feature: Here's what it looks like with clustering disabled: Before running the test, I would expect one unclustered feature, and as many as 10 features that have been clustered. (I say "as many as" because some could be off-screen.) However, running the test indicates 5 unclustered features, and 5 clustered features. Four of those features said to be unclustered are such because their shapes don't intersect the cluster graphic, and therefore are invisible, but returned by hitTest nonetheless. This kind of workflow is the only way I can think of to determine if a feature is clustered or not. But with hitTest implemented the way it is, the results here are unreliable.
... View more
08-09-2024
05:16 PM
|
1
|
0
|
1278
|
|
POST
|
This thread shows how to get a reference to features after the user has confirmed to delete them, but prior to them actually being deleted.
... View more
08-07-2024
02:21 PM
|
1
|
0
|
713
|
|
POST
|
If I'm understanding this correctly, I think the problem is that FeatureLayer ignores the symbol property of its graphics. If you want each graphic to be rendered uniquely, I think your best bet is to create and populate a UniqueValueRenderer object, and set it to the layer's renderer property. You could add the url to the graphic's attributes, and use that as the field for your renderer. Also, if you've created a code block in one of your posts, you can double-click it to re-open and edit.
... View more
08-07-2024
09:37 AM
|
1
|
3
|
1372
|
|
POST
|
Nice, good to hear you got it working. The description of the arguments object here is definitely better than what I would come up with. By calling apply with the arguments object, it ensures the function being called is executed with the same arguments (if any) that were passed to the current function.
... View more
08-07-2024
09:24 AM
|
1
|
1
|
971
|
|
POST
|
It looks like they've taken down the 3.x links. The direct URLs still work though...for now. Go here, copy the URL from the JSON text it outputs, and paste back into your browser.
... View more
08-06-2024
03:04 PM
|
1
|
0
|
3834
|
|
POST
|
Do you have a constraint preventing you from hosting the API locally? I would definitely recommend hosting it to anybody that can, since it gives more control and less dependencies on third parties.
... View more
08-06-2024
01:35 PM
|
0
|
2
|
3843
|
|
POST
|
There doesn't seem to be a "natural" way to do this since by the time "edits" has been fired, the feature has been deleted, with no way to get its info from the layer anymore. The widget otherwise provides nothing like a "before-edits" event, which would be helpful in this regard. I see a couple options here though. First, you could put logic between the "edits" event and your changes getting pushed to the server. In this intermediate step, you could get the feature's data from the server before sending the information to the server about the feature being deleted. This kind of workflow would require an extra server request being added to your overall workflow, and so would be somewhat inefficient, since you'd be sending a network request to get information that existed on your client less than a second ago. The other option is to add a hack that gets you a reference to the feature before it gets deleted. The best way appears to be by overriding the deleteFeatureFromWorkflow method of the Editor widget, because this method gets called immediately after the final "Delete" button on the widget is clicked, but before the delete operation actually takes place. Therefore, this would be the ideal place to examine the attributes of any features being deleted: var editor = new Editor({view:view});
var deleteFeatureFromWorkflow = editor.deleteFeatureFromWorkflow;
editor.deleteFeatureFromWorkflow = function() {
//This is the array of graphics being deleted
var graphics = editor.activeWorkflow.data.candidates;
graphics.forEach(function(graphic) {
//Do something with the graphic reference here
console.log("identifiedMarkerID: " + graphic.attributes.identifiedMarkerID;
});
return deleteFeatureFromWorkflow.apply(this, arguments);
};
... View more
08-06-2024
11:05 AM
|
1
|
3
|
993
|
|
POST
|
Yes, the PrintingTools service is a built-in service that comes with ArcGIS Server, so if you want to host it, you would need to install and license ArcGIS Server on your own server. More information is available here.
... View more
08-06-2024
10:29 AM
|
0
|
0
|
408
|
|
POST
|
I see...one of the main problems here is you won't be able to use "declare" anymore. After migration, my modules use one of two interchangeable approaches: a vanilla implementation, or extending Accessor: Vanilla: define(["esri/Color", "esri/rest/geoprocessor/execute"], function(Color, Geoprocessor) {
var a = function() {
function b(options) {
//this is the constructor
}
b.prototype.isAvailable = function(installation) {
//this is an example member function
};
b.prototype.activate = function(toolParams) {
//this is another example member function
};
return b;
}();
a.prototype.declaredClass = "myPackage.myFolder.myModule"; //optional
return a;
}); Extending Accessor (note, uses undocumented "Evented" class to add support for emitting and handling events with "emit" and "on" like in 3.x): define(["esri/Color", "esri/core/Accessor", "esri/core/Evented", "esri/rest/geoprocessor/execute"], function (Color, Accessor, Evented, Geoprocessor) {
return Evented.EventedMixin(Accessor).createSubclass({
declaredClass: "myPackage.myFolder.myModule", //optional
constructor: function(options) {
//this is the constructor
},
isAvailable: function(installation) {
//this is an example member function
},
activate: function(toolParams) {
//this is another example member function
}
});
}); The error message in your console also indicates it's looking for dojo modules in the ESRI JSAPI, but that was removed in 4.20 (which was what my first post was about). It also shows you're using the API from ESRI's server, but my discussion from the previous post assumes an environment where the API has been downloaded and installed on the local server.
... View more
08-05-2024
12:20 PM
|
1
|
0
|
3896
|
|
POST
|
Yes, it's still valid, but getting everything set up is slightly more complicated than previously. Migrating from 3.x would've been a bit easier prior to 4.20, because dojo was still packaged with the API in those days. When I migrated our platform in the days of 4.16 - 4.18, I was able to eliminate about 94% of the references to dojo, but some of those remaining references still exist to this day (although with charting components hopefully coming out of beta in the near future, we might be ready for the final push to remove dojo entirely). When the dojo-less 4.20 came along, I thought "oh, I'll just load dojo from the 3.x install instead", but it didn't work out that way, because the 3.x loader and the 4.x loader didn't play nice together. To make this work, I ended up copying the dojo, dijit, and dojox folders from the 3.x install into the 4.x install like so: The screenshot below shows the code for loading dojo separately from the Maps SDK for Javascript: For good measure, the screenshot below shows the value of the "baseURL" setting in the dojo.js file which matches the environment displayed above: Hope that helps...
... View more
08-05-2024
11:19 AM
|
0
|
0
|
3904
|
|
POST
|
The root cause of why event handlers within your HTML tags are ignored is discussed in this thread, along with some suggested workarounds.
... View more
07-26-2024
12:44 PM
|
0
|
1
|
1227
|
|
POST
|
For the very few people interested (if any at all), this occurs in 3.x as well. The following hack to the _onExtentChangeHandler function of DynamicMapServiceLayer appears to do the job: var _onExtentChangeHandler = DynamicMapServiceLayer.prototype._onExtentChangeHandler;
DynamicMapServiceLayer.prototype._onExtentChangeHandler = function(a, b, c) {
if ((a) && (a.declaredClass == "esri.geometry.Extent")) {
var extents = a.normalize();
if (extents.length == 2) {
var ext1 = extents[0];
var ext2 = new Extent(ext1.xmin, ext1.ymin, ext1.xmax + extents[1].getWidth(), ext1.ymax, ext1.spatialReference);
ext2._normalize = function() { return this; };
_onExtentChangeHandler.call(this, ext2, b, c);
return;
}
}
_onExtentChangeHandler.apply(this, arguments);
}; Note, only tested with 3.46...
... View more
07-22-2024
02:21 PM
|
0
|
0
|
445
|
|
POST
|
The lengths of each segment aren't included in the event object, but if you have a reference to the polyline object, you can calculate them without much trouble. You would just need to (1) step through the polyline's points 2 at a time (perhaps using getPoint), (2) create a new Polyline object from those two points, and then (3) get the distance for the new 2-point polyline from geometryEngine.geodesicLength.
... View more
07-16-2024
10:09 AM
|
1
|
0
|
854
|
|
POST
|
I haven't tested this, but it seems likely to me that the problem you're experiencing is related to having two SketchViewModels in play at the same time. In the JS code, one is found on line 108, and the other on line 147. Since it doesn't appear that you're actually using the Sketch widget instantiated on line 103, you might try removing it altogether.
... View more
07-15-2024
04:26 PM
|
0
|
0
|
898
|
|
POST
|
The spinner you're talking about is a useful but undocumented widget with the path esri/widgets/Spinner. I've updated the sample's code below to show how it can be used. If you click somewhere on the map, the spinner will show. If you then click somewhere else, it will be hidden. You can repeat the process as many times as you like. In short, I added or updated lines 35, 36, 48-64, and 125. Since this is an undocumented feature, it may be removed or altered in a future release without notice, so using it is at your own risk. <html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Custom popup actions per feature attribute | Sample | ArcGIS Maps SDK for JavaScript 4.30</title>
<style>
body {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#viewDiv {
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.30/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/core/reactiveUtils",
"esri/popup/content/TextContent",
"esri/widgets/Spinner"
], (Map, MapView, FeatureLayer, reactiveUtils, TextContent, Spinner) => {
const map = new Map({
basemap: "streets-navigation-vector"
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [-101.94981250000075, 41.20977753557709],
zoom: 5
});
const spinner = new Spinner({view:view});
view.ui.add(spinner, {key:"a-unique-key-you-make-up", position:"manual"});
view.on("click", function(evt) {
if (spinner.location)
hideSpinner();
else
showSpinner(evt.mapPoint);
});
function hideSpinner() {
spinner.location = null;
spinner.hide();
}
function showSpinner(point) {
spinner.show({location:point});
}
/********************
* Add feature layer
********************/
// sampling of breweries
const featureLayer = new FeatureLayer({
url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/OpenBeerDB/FeatureServer/0",
popupTemplate: {
title: "{name}",
outFields: ["*"],
lastEditInfoEnabled: false,
fieldInfos: [
{
fieldName: "name"
},
{
fieldName: "address1",
label: "address"
},
{
fieldName: "city"
},
{
fieldName: "state"
},
{
fieldName: "phone"
},
{
fieldName: "website"
}
],
actions: [
{
id: "find-brewery",
image: "https://developers.arcgis.com/javascript/latest/sample-code/popup-custom-action/live/beer.png",
title: "Brewery Info"
}
],
content: formatContent
}
});
// Use a function to format the content of the popup
function formatContent(event) {
const attributes = event.graphic.attributes;
let text = "";
// Only display the attributes if they exist
text += attributes.website
? `Brewery: <a href="${attributes.website}">${attributes.name}</a><br>`
: `Brewery: ${attributes.name}<br>`;
text += attributes.address1 ? `Address:<br>${attributes.address1}<br>` : `Located in: `;
text += attributes.city && attributes.state ? `${attributes.city},${attributes.state}<br>` : ``;
text += attributes.phone !== null ? `Phone:${attributes.phone}` : ``;
let textElement = new TextContent({
text: text
});
return [textElement];
}
// map.add(featureLayer);
view.when(() => {
// Watch for when features are selected
reactiveUtils.watch(
() => view.popup.selectedFeature,
(graphic) => {
if (graphic) {
// Set the action's visible property to true if the 'website' field value is not null, otherwise set it to false
const graphicTemplate = graphic.getEffectivePopupTemplate();
graphicTemplate.actions.items[0].visible = graphic.attributes.website ? true : false;
}
}
);
// Watch for the trigger-action event on the popup
reactiveUtils.on(
() => view.popup,
"trigger-action",
(event) => {
if (event.action.id === "find-brewery") {
const attributes = view.popup.selectedFeature.attributes;
// Get the 'website' field attribute
const info = attributes.website;
// Make sure the 'website' field value is not null
if (info) {
// Open up a new browser using the URL value in the 'website' field
window.open(info.trim());
}
}
}
);
});
});
</script>
</head>
<body class="light">
<div id="viewDiv" class="esri-widget"></div>
</body>
</html>
... View more
07-12-2024
10:18 AM
|
1
|
0
|
2236
|
| Title | Kudos | Posted |
|---|---|---|
| 1 | 12-09-2025 09:35 AM | |
| 2 | 12-09-2025 09:06 AM | |
| 1 | 11-26-2025 12:29 PM | |
| 3 | 11-26-2025 09:11 AM | |
| 1 | 10-02-2025 01:14 PM |