Skip to content

Commit 5594281

Browse files
committed
Add new "Zoom To Feature" widget.
1 parent 5e07424 commit 5594281

File tree

8 files changed

+451
-1
lines changed

8 files changed

+451
-1
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ Used in conjunction with the [Attributes Tables](https://github.com/tmcgee/cmv-w
4545

4646
---
4747
### Zoom to Feature
48-
Coming Soon
48+
A simple widget to provide a dropdown list of features to zoom the ma to. Similar to bookmarks but driven by actual data in a Map Service.
49+
#####[Documentation](https://github.com/tmcgee/cmv-widgets/tree/master/widgets/ZoomToFeature/README.md)
50+
#####[Demo](http://tmcgee.github.io/cmv-widgets/demo.html?config=zoomToFeature)
51+
![Screenshot](https://tmcgee.github.io/cmv-widgets/images/zoomToFeature1.jpg)

config/zoomToFeature.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
define([
2+
], function () {
3+
4+
return {
5+
isDebug: false,
6+
7+
mapOptions: {
8+
basemap: 'streets',
9+
center: [-96.59179687497497, 39.09596293629694],
10+
zoom: 5,
11+
sliderStyle: 'small'
12+
},
13+
14+
titles: {
15+
header: 'CMV Zoom To Feature Widget',
16+
subHeader: 'This is an example of zooming to a feature',
17+
pageTitle: 'CMV Zoom To Feature Widget'
18+
},
19+
20+
collapseButtonsPane: 'center', //center or outer
21+
22+
operationalLayers: [],
23+
24+
widgets: {
25+
zoomToFeature: {
26+
include: true,
27+
id: 'zoomToFeature',
28+
type: 'titlePane',
29+
title: 'Zoom to A California County',
30+
position: 0,
31+
open: true,
32+
path: 'widgets/ZoomToFeature',
33+
options: {
34+
map: true,
35+
// you can customize the button text
36+
i18n: {
37+
selectFeature: "Select A County"
38+
},
39+
url: 'http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/2',
40+
field: 'NAME',
41+
where: 'STATE_FIPS = \'06\''
42+
}
43+
}
44+
}
45+
};
46+
});

images/zoomToFeature1.jpg

174 KB
Loading

widgets/ZoomToFeature.js

+332
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
define([
2+
'dojo/_base/declare',
3+
'dijit/_WidgetBase',
4+
'dijit/_TemplatedMixin',
5+
'dijit/_WidgetsInTemplateMixin',
6+
'dojo/_base/lang',
7+
'dojo/_base/array',
8+
'dojo/on',
9+
'dojo/keys',
10+
'dojo/store/Memory',
11+
'esri/tasks/QueryTask',
12+
'esri/tasks/query',
13+
'esri/geometry/Extent',
14+
'esri/layers/GraphicsLayer',
15+
'esri/graphic',
16+
'esri/renderers/SimpleRenderer',
17+
'esri/symbols/SimpleMarkerSymbol',
18+
'esri/symbols/SimpleLineSymbol',
19+
'esri/symbols/SimpleFillSymbol',
20+
'esri/graphicsUtils',
21+
'dojo/text!./ZoomToFeature/templates/ZoomToFeature.html',
22+
'dojo/i18n!./ZoomToFeature/nls/resources',
23+
'dijit/form/Form',
24+
'dijit/form/FilteringSelect',
25+
'xstyle/css!./ZoomToFeature/css/ZoomToFeature.css'
26+
], function (declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, lang, array, on, keys, Memory, QueryTask, Query, Extent, GraphicsLayer, Graphic, SimpleRenderer, SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol, graphicsUtils, template, i18n) {
27+
return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
28+
widgetsInTemplate: true,
29+
templateString: template,
30+
defaultI18n: i18n,
31+
i18n: {},
32+
baseClass: 'cmwZoomToFeatureWidget',
33+
34+
// url of the MapServer to Query
35+
url: null,
36+
37+
// description field for display in drop-down list
38+
field: null,
39+
40+
// where clause to filter the results
41+
where: '1=1',
42+
43+
// Spatial Reference. uses the map's spatial reference if none provided
44+
spatialReference: null,
45+
46+
// Use 0.0001 for decimal degrees (wkid 4326)
47+
// or 500 for meters/feet
48+
pointExtentSize: null,
49+
50+
// default symbology for found features
51+
defaultSymbols: {
52+
point: {
53+
type: 'esriSMS',
54+
style: 'esriSMSCircle',
55+
size: 25,
56+
color: [0, 255, 255, 32],
57+
angle: 0,
58+
xoffset: 0,
59+
yoffset: 0,
60+
outline: {
61+
type: 'esriSLS',
62+
style: 'esriSLSSolid',
63+
color: [0, 255, 255, 255],
64+
width: 2
65+
}
66+
},
67+
polyline: {
68+
type: 'esriSLS',
69+
style: 'esriSLSSolid',
70+
color: [0, 255, 255, 255],
71+
width: 2
72+
},
73+
polygon: {
74+
type: 'esriSFS',
75+
style: 'esriSFSSolid',
76+
color: [0, 255, 255, 32],
77+
outline: {
78+
type: 'esriSLS',
79+
style: 'esriSLSSolid',
80+
color: [0, 255, 255, 255],
81+
width: 2
82+
}
83+
}
84+
},
85+
86+
features: null,
87+
featureStore: null,
88+
featureIdx: null,
89+
90+
postMixInProperties: function () {
91+
this.inherited(arguments);
92+
this.i18n = this.mixinDeep(this.defaultI18n, this.i18n);
93+
},
94+
postCreate: function () {
95+
this.inherited(arguments);
96+
97+
if (!this.spatialReference) {
98+
this.spatialReference = this.map.spatialReference.wkid;
99+
}
100+
if (!this.pointExtentSize) {
101+
if (this.spatialReference === 4326) { // special case for geographic lat/lng
102+
this.pointExtentSize = 0.0001;
103+
} else {
104+
this.pointExtentSize = 250; // could be feet or meters
105+
}
106+
}
107+
108+
this.createGraphicLayers();
109+
this.createGraphicRenderers();
110+
111+
// allow pressing enter key to initiate the search
112+
this.own(on(this.featureSelectDijit, 'keyup', lang.hitch(this, function (evt) {
113+
if (evt.keyCode === keys.ENTER) {
114+
this.search();
115+
}
116+
})));
117+
118+
if (this.url) {
119+
this.getFeatures();
120+
}
121+
122+
},
123+
124+
createGraphicRenderers: function () {
125+
var pointSymbol = null,
126+
polylineSymbol = null,
127+
polygonSymbol = null,
128+
pointRenderer = null,
129+
polylineRenderer = null,
130+
polygonRenderer = null;
131+
132+
var symbols = lang.mixin({}, this.symbols);
133+
// handle each property to preserve as much of the object heirarchy as possible
134+
symbols = {
135+
point: lang.mixin(this.defaultSymbols.point, symbols.point),
136+
polyline: lang.mixin(this.defaultSymbols.polyline, symbols.polyline),
137+
polygon: lang.mixin(this.defaultSymbols.polygon, symbols.polygon)
138+
};
139+
140+
if (symbols.point && this.pointGraphics) {
141+
pointSymbol = new SimpleMarkerSymbol(symbols.point);
142+
pointRenderer = new SimpleRenderer(pointSymbol);
143+
pointRenderer.label = 'Search Results (Points)';
144+
pointRenderer.description = 'Search results (Points)';
145+
this.pointGraphics.setRenderer(pointRenderer);
146+
}
147+
148+
if (symbols.polyline && this.polylineGraphics) {
149+
polylineSymbol = new SimpleLineSymbol(symbols.polyline);
150+
polylineRenderer = new SimpleRenderer(polylineSymbol);
151+
polylineRenderer.label = 'Search Results (Lines)';
152+
polylineRenderer.description = 'Search Results (Lines)';
153+
this.polylineGraphics.setRenderer(polylineRenderer);
154+
}
155+
156+
if (symbols.polygon && this.polylineGraphics) {
157+
polygonSymbol = new SimpleFillSymbol(symbols.polygon);
158+
polygonRenderer = new SimpleRenderer(polygonSymbol);
159+
polygonRenderer.label = 'Search Results (Polygons)';
160+
polygonRenderer.description = 'Search Results (Polygons)';
161+
this.polygonGraphics.setRenderer(polygonRenderer);
162+
}
163+
},
164+
165+
createGraphicLayers: function () {
166+
// points
167+
this.pointGraphics = new GraphicsLayer({
168+
id: this.id + '_Points',
169+
title: this.id + ' Points'
170+
});
171+
172+
// polyline
173+
this.polylineGraphics = new GraphicsLayer({
174+
id: this.id + '_Lines',
175+
title: this.id + ' Lines'
176+
});
177+
178+
// polygons
179+
this.polygonGraphics = new GraphicsLayer({
180+
id: this.id + '_Polygons',
181+
title: this.id + ' Polygons'
182+
});
183+
184+
this.map.addLayer(this.polygonGraphics);
185+
this.map.addLayer(this.polylineGraphics);
186+
this.map.addLayer(this.pointGraphics);
187+
},
188+
189+
getFeatures: function () {
190+
var query = new Query();
191+
query.outFields = [this.field];
192+
query.where = this.where;
193+
query.returnGeometry = true;
194+
//query.returnDistinctValues = true;
195+
query.outSpatialReference = {
196+
wkid: this.spatialReference
197+
};
198+
199+
var queryTask = new QueryTask(this.url);
200+
queryTask.execute(query, lang.hitch(this, 'populateList'));
201+
},
202+
203+
//Populate the dropdown list box with unique values
204+
populateList: function (results) {
205+
this.features = results.features;
206+
207+
var values = [], k = 0, field = this.field;
208+
array.forEach (this.features, function(feature) {
209+
values.push({
210+
id: k++,
211+
name: feature.attributes[field]
212+
});
213+
});
214+
215+
this.featureStore = new Memory({
216+
data: values
217+
});
218+
this.featureSelectDijit.set('store', this.featureStore);
219+
this.featureSelectDijit.set('disabled', false);
220+
},
221+
222+
onFeatureChange: function (featureIdx) {
223+
if (featureIdx >= 0 && featureIdx < this.features.length) {
224+
this.featureIdx = featureIdx;
225+
this.search();
226+
}
227+
},
228+
229+
search: function () {
230+
this.clearFeatures();
231+
232+
if (!this.featureIdx) {
233+
return;
234+
}
235+
var feature = this.features[this.featureIdx];
236+
if (feature) {
237+
this.highlightFeature(feature);
238+
extent = this.getGraphicsExtent([feature]);
239+
if (extent) {
240+
this.zoomToExtent(extent);
241+
}
242+
}
243+
},
244+
245+
clearResults: function () {
246+
this.featureIdx = null;
247+
this.clearFeatures();
248+
this.clearButtonDijit.set('disabled', true);
249+
this.featureSelectDijit.reset();
250+
},
251+
252+
clearFeatures: function () {
253+
this.pointGraphics.clear();
254+
this.polylineGraphics.clear();
255+
this.polygonGraphics.clear();
256+
},
257+
258+
highlightFeature: function (feature) {
259+
switch (feature.geometry.type) {
260+
case 'point':
261+
// only add points to the map that have an X/Y
262+
if (feature.geometry.x && feature.geometry.y) {
263+
graphic = new Graphic(feature.geometry);
264+
this.pointGraphics.add(graphic);
265+
this.clearButtonDijit.set('disabled', false);
266+
}
267+
break;
268+
case 'polyline':
269+
// only add polylines to the map that have paths
270+
if (feature.geometry.paths && feature.geometry.paths.length > 0) {
271+
graphic = new Graphic(feature.geometry);
272+
this.polylineGraphics.add(graphic);
273+
this.clearButtonDijit.set('disabled', false);
274+
}
275+
break;
276+
case 'polygon':
277+
// only add polygons to the map that have rings
278+
if (feature.geometry.rings && feature.geometry.rings.length > 0) {
279+
graphic = new Graphic(feature.geometry, null, {
280+
ren: 1
281+
});
282+
this.polygonGraphics.add(graphic);
283+
this.clearButtonDijit.set('disabled', false);
284+
}
285+
break;
286+
default:
287+
}
288+
},
289+
290+
zoomToExtent: function (extent) {
291+
this.map.setExtent(extent.expand(1.5));
292+
},
293+
getGraphicsExtent: function (graphics) {
294+
var extent;
295+
if (graphics && graphics.length > 0) {
296+
extent = graphicsUtils.graphicsExtent(graphics);
297+
if (extent.xmin === extent.xmax || extent.ymin === extent.ymax) {
298+
extent = this.expandExtent(extent);
299+
}
300+
}
301+
return extent;
302+
},
303+
304+
expandExtent: function (extent) {
305+
extent.xmin -= this.pointExtentSize;
306+
extent.ymin -= this.pointExtentSize;
307+
extent.xmax += this.pointExtentSize;
308+
extent.ymax += this.pointExtentSize;
309+
return extent;
310+
},
311+
312+
mixinDeep: function(dest, source) {
313+
//Recursively mix the properties of two objects
314+
var empty = {};
315+
for (var name in source) {
316+
if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) {
317+
try {
318+
if ( source[name].constructor==Object ) {
319+
dest[name] = this.mixinDeep(dest[name], source[name]);
320+
} else {
321+
dest[name] = source[name];
322+
}
323+
} catch(e) {
324+
// Property in destination object not set. Create it and set its value.
325+
dest[name] = source[name];
326+
}
327+
}
328+
}
329+
return dest;
330+
}
331+
});
332+
});

0 commit comments

Comments
 (0)