Advanced Dynamic Map Service Features
This guide covers the advanced capabilities of the DynamicMapService, including server-side labeling, time-awareness, statistical queries, map export, and batch operations.
Server-Side Labeling
Label Configuration
Apply custom text labels with full styling control directly on ArcGIS Server:
service.setLayerLabels(layerId, {
labelExpression: '[field_name]', // Field expression - use actual field name
labelExpressionInfo: { // Advanced expressions (optional)
expression: '$feature.state_name + " (" + $feature.pop2000 + ")"',
returnType: 'String'
},
symbol: {
type: 'esriTS',
color: [255, 255, 255, 255], // Text color (RGBA)
backgroundColor: [0, 0, 0, 128], // Background color
borderLineColor: [0, 0, 0, 255], // Border color
borderLineSize: 1, // Border width
font: {
family: 'Arial',
size: 12,
style: 'normal', // 'normal' | 'italic' | 'oblique'
weight: 'bold' // 'normal' | 'bold'
},
horizontalAlignment: 'center', // 'left' | 'right' | 'center'
verticalAlignment: 'middle' // 'baseline' | 'top' | 'middle' | 'bottom'
},
minScale: 0, // Minimum scale for visibility
maxScale: 25000000, // Maximum scale for visibility
labelPlacement: 'esriServerPolygonPlacementAlwaysHorizontal',
where: 'POP2000 > 1000000' // Optional filter for labeled features
});
Label Visibility Control
// Toggle labels on/off
service.setLayerLabelsVisible(layerId, true);
service.setLayerLabelsVisible(layerId, false);
Time-Aware Layer Management
Time Configuration
Configure temporal data visualization for time-enabled layers:
service.setLayerTimeOptions(layerId, {
useTime: true,
timeExtent: [startTimestamp, endTimestamp], // Time range in milliseconds
timeOffset: 5, // Time offset value
timeOffsetUnits: 'esriTimeUnitsHours' // Offset units
});
Time Animation
Animate through temporal data with frame callbacks:
await service.animateTime({
from: new Date('2023-01-01'),
to: new Date('2023-12-31'),
intervalMs: 1000, // Frame interval
loop: true, // Loop animation
onFrame: (currentTime, progress) => {
console.log(`Current time: ${currentTime.toISOString()}`);
console.log(`Progress: ${(progress * 100).toFixed(1)}%`);
// Update UI or perform other actions
updateTimeSlider(currentTime);
},
onComplete: () => {
console.log('Animation completed');
}
});
Statistical Analysis
Layer Statistics
Get statistical analysis results for numeric fields:
const statistics = await service.getLayerStatistics(layerId, [
{
statisticType: 'count',
onStatisticField: 'OBJECTID',
outStatisticFieldName: 'total_count'
},
{
statisticType: 'sum',
onStatisticField: 'POPULATION',
outStatisticFieldName: 'total_population'
},
{
statisticType: 'avg',
onStatisticField: 'POPULATION',
outStatisticFieldName: 'avg_population'
},
{
statisticType: 'min',
onStatisticField: 'POPULATION',
outStatisticFieldName: 'min_population'
},
{
statisticType: 'max',
onStatisticField: 'POPULATION',
outStatisticFieldName: 'max_population'
}
], {
where: 'POPULATION > 0', // Optional filter
groupByFieldsForStatistics: 'STATE_NAME' // Optional grouping
});
console.log('Statistics results:', statistics);
Feature Queries
Query features with spatial and attribute filtering:
const features = await service.queryLayerFeatures(layerId, {
where: 'POPULATION > 1000000', // Attribute filter
geometry: { // Spatial filter (GeoJSON)
type: 'Polygon',
coordinates: [[[-125, 32], [-114, 32], [-114, 42], [-125, 42], [-125, 32]]]
},
geometryType: 'esriGeometryPolygon',
spatialRel: 'esriSpatialRelIntersects', // Spatial relationship
returnGeometry: true, // Include geometry in results
outFields: ['STATE_NAME', 'POPULATION', 'AREA'], // Fields to return
orderByFields: 'POPULATION DESC', // Sort results
resultOffset: 0, // Pagination offset
resultRecordCount: 25, // Limit results
returnCountOnly: false, // Return full features
returnIdsOnly: false // Return object IDs only
});
console.log(`Found ${features.features.length} features`);
features.features.forEach(feature => {
console.log(feature.attributes.STATE_NAME, feature.attributes.POPULATION);
});
High-Resolution Map Export
Export styled maps as high-resolution images:
const blob = await service.exportMapImage({
bbox: [-125, 32, -114, 42], // Extent [west, south, east, north]
size: [1200, 800], // Image dimensions [width, height]
dpi: 300, // Resolution (default: 96)
format: 'png', // Output format
transparent: true, // Transparent background
bboxSR: 4326, // Coordinate system of bbox
imageSR: 3857, // Output coordinate system
layerDefs: { // Layer-specific filters
'2': 'POPULATION > 1000000',
'3': 'AREA > 1000'
},
dynamicLayers: service.esriServiceOptions.dynamicLayers, // Apply current styling
gdbVersion: 'DEFAULT', // Geodatabase version
historicMoment: Date.now() // Historic moment for archive data
});
// Download the exported image
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'exported-map.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
Supported Export Formats
'png'- Portable Network Graphics (default)'png8'- 8-bit PNG'png24'- 24-bit PNG'png32'- 32-bit PNG'jpg'- JPEG'gif'- Graphics Interchange Format'bmp'- Bitmap'svg'- Scalable Vector Graphics'pdf'- Portable Document Format
Metadata Discovery
Layer Information
Get comprehensive layer metadata:
// Get detailed layer information
const layerInfo = await service.getLayerInfo(layerId);
console.log('Layer name:', layerInfo.name);
console.log('Layer type:', layerInfo.type);
console.log('Geometry type:', layerInfo.geometryType);
console.log('Fields:', layerInfo.fields);
console.log('Extent:', layerInfo.extent);
// Get just field definitions
const fields = await service.getLayerFields(layerId);
fields.forEach(field => {
console.log(`${field.name}: ${field.type} (${field.alias})`);
});
// Get spatial extent
const extent = await service.getLayerExtent(layerId);
console.log('Extent:', {
xmin: extent.xmin,
ymin: extent.ymin,
xmax: extent.xmax,
ymax: extent.ymax,
spatialReference: extent.spatialReference
});
Service Discovery
Discover all available layers in the service:
const layers = await service.discoverLayers();
layers.forEach(layer => {
console.log(`Layer ${layer.id}: ${layer.name} (${layer.type})`);
});
Legend Generation
Get symbology information for layers:
// Generate legend for all layers
const allLegends = await service.generateLegend();
// Generate legend for specific layers
const specificLegends = await service.generateLegend([1, 2, 3]);
specificLegends.forEach(layerLegend => {
console.log(`Layer ${layerLegend.layerId}: ${layerLegend.layerName}`);
layerLegend.legend.forEach(legendItem => {
console.log(` - ${legendItem.label}: ${legendItem.url}`);
});
});
Batch Operations & Transactions
Bulk Updates
Apply multiple layer operations atomically:
service.setBulkLayerProperties([
{
layerId: 1,
operation: 'visibility',
value: true
},
{
layerId: 1,
operation: 'renderer',
value: {
type: 'simple',
symbol: { type: 'esriSFS', color: [255, 0, 0, 128] }
}
},
{
layerId: 2,
operation: 'filter',
value: {
field: 'POPULATION',
op: '>',
value: 5000000
}
},
{
layerId: 3,
operation: 'definition',
value: 'STATE_NAME LIKE \'%CALIFORNIA%\''
},
{
layerId: 3,
operation: 'labels',
value: [{
labelExpression: '[STATE_NAME]',
symbol: {
type: 'esriTS',
color: [0, 0, 0, 255],
font: { family: 'Arial', size: 10 }
}
}]
},
{
layerId: 4,
operation: 'time',
value: {
useTime: true,
timeExtent: [Date.now() - 86400000, Date.now()]
}
}
]);
Transaction Management
Control when map updates are applied:
// Begin a transaction - changes won't be applied immediately
service.beginUpdate();
console.log('In transaction:', service.isInTransaction); // true
// Make multiple changes
service.setLayerVisibility(1, false);
service.setLayerRenderer(2, customRenderer);
service.setLayerFilter(3, populationFilter);
// Map hasn't updated yet - all changes are pending
// Option 1: Commit all changes at once
service.commitUpdate();
console.log('In transaction:', service.isInTransaction); // false
// Option 2: Rollback all changes (if called instead of commit)
// service.rollbackUpdate();
Error Handling
All async methods properly handle and throw errors:
try {
const statistics = await service.getLayerStatistics(layerId, statisticFields);
console.log('Statistics:', statistics);
} catch (error) {
console.error('Statistics query failed:', error.message);
}
try {
const features = await service.queryLayerFeatures(layerId, queryOptions);
console.log('Features:', features);
} catch (error) {
console.error('Feature query failed:', error.message);
}
try {
const blob = await service.exportMapImage(exportOptions);
console.log('Export successful');
} catch (error) {
console.error('Export failed:', error.message);
}
Performance Considerations
Server-Side Processing
- All styling, filtering, and labeling is processed on ArcGIS Server
- Reduces client-side memory usage and processing time
- Consistent rendering across different devices and browsers
Batch Operations
- Use
setBulkLayerProperties()for multiple simultaneous changes - Use transactions (
beginUpdate/commitUpdate) to prevent flickering - Group related operations together to minimize server requests
Caching
- Layer metadata and field information are cached after first request
- Statistics and query results are not cached and reflect current data
- Legend information is cached based on current layer symbology
Best Practices
- Use Structured Filters - Prefer structured filters over raw SQL for better type safety
- Batch Updates - Group multiple layer changes using bulk operations
- Error Handling - Always wrap async operations in try/catch blocks
- Scale Ranges - Set appropriate min/max scales for labels and layer visibility
- Field Selection - Specify only needed fields in queries to improve performance
- Pagination - Use
resultRecordCountandresultOffsetfor large datasets