/** * @name Google Maps Layer for ArcGIS Server JavaScript API * @author: Nianwei Liu * @fileoverview *
Use Google Maps in application built on ESRI ArcGIS Server JavaScript API. *
*/ // Change log: //2012-07-15: v2.0, update to jsapi3.0/dojo1.7, simplified. // drop features related to sublayers for google maps, client code can do it after get reference to gmap instance. // constructor parameter syntax changed slightly for BasemapGallery. // streetview control is on by default. // no xbuilt. issues with loading the xdomain built from googlecode.com while loading jsapi from arcgisonline. //2011-12-15: v1.07, update to jsapi2.6, oblique rotation, work with esri BasemapGallery, delayed of loading of API until set as visible //2011-10-24: v1.05, working with basemapcontrol, styled options //2011-10-18: v1.04, xd built //2011-10-17: v1.03, added support for StreetView, Sub layers (Traffic, Point of Interest etc) //2011-10-05: fixed issues with Chrome, IE7, IE8 //2011-08-11: updated for JSAPI 2.4. changed package. //define("agsjs/layers/GoogleMapsLayer", ["dojo", "dijit", "dojox", "dojo/require","esri/dijit/BasemapGallery"], function(dojo, dijit, dojox) { /*global dojo esri agsjs */ dojo.require('esri.dijit.BasemapGallery'); dojo.provide('agsjs.layers.GoogleMapsLayer'); dojo.declare("agsjs.layers.GoogleMapsLayer", esri.layers.Layer, { /** * @name GoogleMapsLayerOptions * @class This is an object literal that specify the option to construct a {@link GoogleMapsLayer}. * @property {String} [id] layerId. * @property {Boolean} [visible] default visibility * @property {Number} [opacity] opacity (0-1) * @property {GoogleMapsAPIOptions} [apiOptions] The options to load Google API. Include property: v, client, sensor, language etc,see {@link GoogleMapsAPIOptions} * @property {google.maps.MapOptions} [mapOptions] optional. The options for construct Google Map instance. See MapOptions. */ /** * @name GoogleMapsAPIOptions * @class This is an object literal that specify the option to load Google Maps API V3. See Google documentation for more information. * @property {Boolean} [sensor] whether GPS device is used. default to false; * @property {Number} [v] API version, 3, 3.1 etc. default to 3. * @property {String} [client] client ID for Google Maps Premier license. * @property {String} [language] language to use for text such as the names for controls, etc, e.g. cn, ja * @property {String} [dir] Bi-directional (Bidi) text. rtl or ltr; * @property {String} [libraries] Additonal libraries (geometry|places|adsense); * */ /** * Create a GoogleMapsLayer using config {@link GoogleMapsLayerOptions} * @name GoogleMapsLayer * @constructor * @class This class allows Google Maps been used in ESRI ArcGIS JavaScript API. * @param {GoogleMapsLayerOptions} opts */ constructor: function(opts) { opts = opts || {}; // this tileInfo does not actually do anything. It simply tricks nav control to // show a slider bar if only gmaps are used, which should be a rare case. this.tileInfo = new esri.layers.TileInfo({ rows: 256, cols: 256, dpi: 96, origin: { x: -20037508.342787, y: 20037508.342787 }, spatialReference: { wkid: 102100 }, lods: [{ level: 0, resolution: 156543.033928, scale: 591657527.591555 }, { level: 1, resolution: 78271.5169639999, scale: 295828763.795777 }, { level: 2, resolution: 39135.7584820001, scale: 147914381.897889 }, { level: 3, resolution: 19567.8792409999, scale: 73957190.948944 }, { level: 4, resolution: 9783.93962049996, scale: 36978595.474472 }, { level: 5, resolution: 4891.96981024998, scale: 18489297.737236 }, { level: 6, resolution: 2445.98490512499, scale: 9244648.868618 }, { level: 7, resolution: 1222.99245256249, scale: 4622324.434309 }, { level: 8, resolution: 611.49622628138, scale: 2311162.217155 }, { level: 9, resolution: 305.748113140558, scale: 1155581.108577 }, { level: 10, resolution: 152.874056570411, scale: 577790.554289 }, { level: 11, resolution: 76.4370282850732, scale: 288895.277144 }, { level: 12, resolution: 38.2185141425366, scale: 144447.638572 }, { level: 13, resolution: 19.1092570712683, scale: 72223.819286 }, { level: 14, resolution: 9.55462853563415, scale: 36111.909643 }, { level: 15, resolution: 4.77731426794937, scale: 18055.954822 }, { level: 16, resolution: 2.38865713397468, scale: 9027.977411 }, { level: 17, resolution: 1.19432856685505, scale: 4513.988705 }, { level: 18, resolution: 0.597164283559817, scale: 2256.994353 }, { level: 19, resolution: 0.298582141647617, scale: 1128.497176 }, { level: 20, resolution: 0.149291070823808, scale: 564.248588 }] }); this.fullExtent = new esri.geometry.Extent({ xmin: -20037508.34, ymin: -20037508.34, xmax: 20037508.34, ymax: 20037508.34, spatialReference: { wkid: 102100 } }); this.initialExtent = new esri.geometry.Extent({ xmin: -20037508.34, ymin: -20037508.34, xmax: 20037508.34, ymax: 20037508.34, spatialReference: { wkid: 102100 } }); this.opacity = opts.opacity || 1; this._mapOptions = opts.mapOptions || {}; this._apiOptions = dojo.mixin({ sensor: false }, opts.apiOptions || {}); /*if (this._mapOptions.streetViewControl == undefined) { this._mapOptions.streetViewControl = false; }*/ this._gmap = null; this.loaded = true;// it seems _setMap will only get called if loaded = true, so set it here first. this.onLoad(this); }, /** * get the wrappedgoogle.maps.Map for further customization.
* @function
* @name GoogleMapsLayer#getGoogleMapInstance
* @return {google.maps.Map}
*/
getGoogleMapInstance: function() {
return this._gmap;
},
// override parent class abstract method
/**********************
* @see http://help.arcgis.com/en/webapi/javascript/arcgis/samples/exp_rasterlayer/javascript/RasterLayer.js
* Internal Properties
*
* _map
* _element
* _context
* _mapWidth
* _mapHeight
* _connects
*
**********************/
_setMap: function(map, container) {
// This overrides an undocumented private method from ESRI API.
// It's possible not to do this, but it requires
// map instance implicitly set to the layer instance, which is a little bit inconvenient.
// this is likely called inside esriMap.addLayer()
this._map = map;
// this style is used to style 0 size divs, mainly for holding it's DOM position but not obstructing events.
var style = {
position: 'absolute',
top: '0px',
left: '0px',
width: '0px',
height: '0px'
};
var element = dojo.create('div', {}, container);
if (this.id) {
element.id = this.id;
}
dojo.style(element, style);
this._element = element;
var div = dojo.create('div', {}, element);
dojo.style(div, style);
dojo.style(div, 'width', (map.width || container.offsetWidth) + 'px');
dojo.style(div, 'height', (map.height || container.offsetHeight) + 'px');
this._gmapDiv = div;
// topDiv is used to mask all esri events in oblique mode.
var tdiv = dojo.create('div', {}, map.id);
tdiv.id = 'gmaps_top_' + div.id;
dojo.style(tdiv, style);
this._topDiv = tdiv;
// controlDiv is used to hold pegman and oblique rotater.
var cdiv = dojo.create('div', {}, map.id);
cdiv.id = 'gmaps_controls_' + div.id;
dojo.style(cdiv, dojo.mixin(style, {
// width: '0px',
// height: '0px',
top: '5px',
left: '5px'
}));
this._controlDiv = cdiv;
//this._container = layersDiv;
// Event connections
this._connects = [];
this._connects.push(dojo.connect(this, 'onVisibilityChange', this, this._visibilityChangeHandler));
this._connects.push(dojo.connect(this, 'onOpacityChange', this, this._opacityChangeHandler));
this.visible = (this.visible === undefined) ? true : this.visible;
if (this.visible) {
this._initGMap();
}
return element;
},
_unsetMap: function(map, layersDiv) {
// see _setMap. Undocumented method, but probably should be public.
dojo.forEach(this._connects, dojo.disconnect, dojo);
if (this._streetView) {
this._streetView.setVisible(false);
}
if (google && google.maps && google.maps.event) {
if (this._gmapTypeChangeHandle)
google.maps.event.removeListener(this._gmapTypeChangeHandle);
if (this._svVisibleHandle)
google.maps.event.removeListener(this._svVisibleHandle);
}
if (this._element)
this._element.parentNode.removeChild(this._element);
dojo.destroy(this._element);
if (this._controlDiv)
this._controlDiv.parentNode.removeChild(this._controlDiv);
dojo.destroy(this._controlDiv);
if (this._topDiv)
this._topDiv.parentNode.removeChild(this._topDiv);
dojo.destroy(this._topDiv);
this._element = this._gmapDiv = this._controlDiv = null;
},
// delayed init and Api loading.
_initGMap: function() {
window.google = window.google || {}; // somehow IE needs this, otherwise complain google.maps namespace;
if (window.google && google.maps) {
var ext = this._map.extent;
var center = this._mapOptions.center || this._esriPointToLatLng(ext.getCenter());
var level = this._map.getLevel();
var myOptions = dojo.mixin({
//disableDefaultUI: true,
center: center,
zoom: (level > -1) ? level : 1,
panControl: false,
mapTypeControl: false,
zoomControl: false
}, this._mapOptions);
if (myOptions.mapTypeId) {
myOptions.mapTypeId = this._getGMapTypeId(myOptions.mapTypeId);
} else {
myOptions.mapTypeId = google.maps.MapTypeId.ROADMAP;
}
var gmap = new google.maps.Map(this._gmapDiv, myOptions);
if (level < 0) {
dojo.connect(this._map, 'onLoad', dojo.hitch(this, function() {
this._setExtent(ext);
}));
}
this._gmap = gmap;
this._setExtent(ext);
this._extentChangeHandle = dojo.connect(this._map, 'onExtentChange', this, this._extentChangeHandler);
this._panHandle = dojo.connect(this._map, 'onPan', this, this._panHandler);
this._resizeHandle = dojo.connect(this._map, 'onResize', this, this._resizeHandler);
// 45 deg need move up regardless of streetview
this._mvHandle = dojo.connect(this._map, 'onMouseMove', dojo.hitch(this, this._moveControls));
this._gmapTypeChangeHandle = google.maps.event.addListener(this._gmap, 'maptypeid_changed', dojo.hitch(this, this._mapTypeChangeHandler));
this._gmapTiltChangeHandle = google.maps.event.addListener(this._gmap, 'tilt_changed', dojo.hitch(this, this._mapTiltChangeHandler));
this.onLoad();
} else if (agsjs.onGMapsApiLoad) {
// did another instance already started loading agsjs API but not done?
// this should be very very rare because one instance of this layer would be sufficient with setMapTypeId.
dojo.connect(agsjs, 'onGMapsApiLoad', this, this._initGMap);
} else {
// this is the first instance that tries to load agsjs API on-demand
agsjs.onGMapsApiLoad = function() {
// do nothing, just needed to dispatch event.
};
dojo.connect(agsjs, 'onGMapsApiLoad', this, this._initGMap);
var script = document.createElement('script');
script.type = 'text/javascript';
var pro = window.location.protocol;
if (pro.toLowerCase().indexOf('http') == -1) {
pro = 'http:';
}
var src = pro + '//maps.googleapis.com/maps/api/js?callback=agsjs.onGMapsApiLoad';
for (var x in this._apiOptions) {
if (this._apiOptions.hasOwnProperty(x)) {
src += '&' + x + '=' + this._apiOptions[x];
}
}
script.src = src;
if (document.getElementsByTagName('head').length > 0) {
document.getElementsByTagName('head')[0].appendChild(script);
} else {
document.body.appendChild(script);
}
}
},
/**
* Sets Opacity
* @name GoogleMapsLayer#setOpacity
* @function
* @param {Number} opacity from 0-1
*/
setOpacity: function(opacity) {
// dojo core should have something do this
if (this._gmapDiv) {
opacity = Math.min(Math.max(opacity, 0), 1);
var st = this._gmapDiv.style;
if (typeof st.opacity !== 'undefined') {
st.opacity = opacity;
} else if (typeof st.filters !== 'undefined') {
st.filters.alpha.opacity = Math.floor(100 * opacity);
} else if (typeof st.filter !== 'undefined') {
st.filter = "alpha(opacity:" + Math.floor(opacity * 100) + ")";
}
}
this.opacity = opacity;
},
/**
* set map type id. e.g GoogleMapsLayer.MAP_TYPE_ROADMAP
*
* @name GoogleMapsLayer#setMapTypeId
* @function
* @param {String} mapTypeId one of GoogleMapsLayer.MAP_TYPE_ROADMAP|MAP_TYPE_HYBRID|MAP_TYPE_STELLITE|MAP_TYPE_TERRIAN
*/
setMapTypeId: function(mapTypeId) {
if (this._gmap) {
this._gmap.setMapTypeId(this._getGMapTypeId(mapTypeId));
this._mapTypeChangeHandler();
} else {
this._mapOptions.mapTypeId = mapTypeId;
}
return;
},
/**
* set map style for customized base map. The style specs are available at Google Documentation.
* This class privide a few pre-defined styles: MAP_STYLE_GRAY|MAP_STYLE_NIGHT.
* @name GoogleMapsLayer#setMapStyle
* @function
* @param {Object[]} styles
*/
setMapStyles: function(styles) {
if (this._styleOptions) {
// if has _styleOptions, set it null means clear it.
styles = styles || [];
}
if (styles) {
if (this._gmap) {
this._styleOptions = styles;
this._gmap.setOptions({
styles: styles
});
} else {
this._mapOptions.styles = styles;
}
}
return;
},
/**
* Fired when Google Map Type (ROAD, SATERLLITE etc) changed
* @name GoogleMapsLayer#onMapTypeChange
* @param {String} mapTypeId
* @event
*/
onMapTypeChange: function(mapTypeId) {
// event
},
_getGMapTypeId: function(type) {
// typically the constants is same, however, if google changes,
// and API is loaded dynamically but map type is specified before API load,
// there is a slight chance they are out of sync, so fix here.
if (google && google.maps) {
switch (type) {
case agsjs.layers.GoogleMapsLayer.MAP_TYPE_ROADMAP:
return google.maps.MapTypeId.ROADMAP;
case agsjs.layers.GoogleMapsLayer.MAP_TYPE_HYBRID:
return google.maps.MapTypeId.HYBRID;
case agsjs.layers.GoogleMapsLayer.MAP_TYPE_SATELLITE:
return google.maps.MapTypeId.SATELLITE;
case agsjs.layers.GoogleMapsLayer.MAP_TYPE_TERRAIN:
return google.maps.MapTypeId.TERRAIN;
}
}
return type;
},
_opacityChangeHandler: function(opacity) {
// this probably should be handled in the core ESRI API using the div returned from _setMap().
this.setOpacity(opacity);
},
_visibilityChangeHandler: function(v) {
if (v) {
esri.show(this._gmapDiv);
//if (!this._svDisabled) {
esri.show(this._controlDiv);
//}
this.visible = true;
if (this._gmap) {
google.maps.event.trigger(this._gmap, 'resize');
this._panHandle = this._panHandle || dojo.connect(this._map, "onPan", this, this._panHandler);
this._extentChangeHandle = this._extentChangeHandle || dojo.connect(this._map, "onExtentChange", this, this._extentChangeHandler);
this._setExtent(this._map.extent);
} else {
this._initGMap();
}
} else {
if (this._gmapDiv) {
esri.hide(this._gmapDiv);
esri.hide(this._controlDiv);
this.visible = false;
if (this._gmap) {
this._map.setExtent(this._latLngBoundsToEsriExtent(this._gmap.getBounds()));
}
if (this._streetView) {
this._streetView.setVisible(false);
}
if (this._panHandle) {
dojo.disconnect(this._panHandle);
this._panHandle = null;
}
if (this._extentChangeHandle) {
dojo.disconnect(this._extentChangeHandle);
this._extentChangeHandle = null;
}
}
}
},
_resizeHandler: function(extent, height, width) {
dojo.style(this._gmapDiv, {
width: this._map.width + "px",
height: this._map.height + "px"
});
google.maps.event.trigger(this._gmap, 'resize');
},
_extentChangeHandler: function(extent, delta, levelChange, lod) {
if (levelChange) {
this._setExtent(extent);
} else {
this._gmap.setCenter(this._esriPointToLatLng(extent.getCenter()));
}
},
_panHandler: function(extent, delta) {
//console.log('pan:'+extent.xmin+','+extent.ymin+','+extent.xmax+','+extent.ymax);
if (this._gmap.getTilt() == 0) {
this._gmap.setCenter(this._esriPointToLatLng(extent.getCenter()));
}
},
_mapTypeChangeHandler: function() {
this._checkZoomLevel();
this.onMapTypeChange(this._gmap.getMapTypeId());
},
_checkZoomLevel: function() {
var id = this._gmap.getMapTypeId();
var types = this._gmap.mapTypes;
var maptype = null;
for (var x in types) {
if (types.hasOwnProperty(x) && x == id) {
maptype = types[x];
break;
}
}
// prevent the case when switch to terrain causing misalignment because terrain only up to level 15.
if (maptype != null) {
var mi = maptype.minZoom;
var mx = maptype.maxZoom;
var z = this._map.getLevel();
if (mx !== undefined && z > mx) {
this._map.setLevel(mx);
}
if (mi != undefined && z < mi) {
this._map.setLevel(mi);
}
}
},
_setExtent: function(extent) {
var ct = this._esriPointToLatLng(extent.getCenter());
var lv = this._map.getLevel();
/*
if (lv >= 0) {
this._gmap.setZoom(lv);
} else {
}*/
// esrimap.getLevel is not reliable. result differnt if first layer is Bing Map vs arcgis tile layer.
// Google maps fit always get a smaller zoom.
this._gmap.fitBounds(this._esriExtentToLatLngBounds(extent.expand(0.5)));
this._gmap.setCenter(ct);
this._checkZoomLevel();
//console.log('gmaps fit:'+this._gmap.getZoom()+' emap:'+lv);
},
// move the street view control on top of map container.
// Esri API prevents mouse event progaginate to lower divs inside map container
// this method sort of move it up so it can be dragged. A little bit hack,
// but as long as stick to a certain version, should still be workable.
_moveControls: function() {
if (this._mvHandle) {
if (!this._gmap) {
dojo.disconnect(this._mvHandle);
this._mvHandle = null;
} else {
if (!this._svMoved) {
this._streetView = this._gmap.getStreetView();
if (this._streetView) {
var sv = dojo.query('.gmnoprint img[src*="cb_scout_sprite"]', this._gmapDiv);
if (sv.length > 0) {
dojo.forEach(sv, function(s, idx) {
dojo.place(s.parentNode.parentNode, this._controlDiv);
}, this);
this._svMoved = true;
this._svVisibleHandle = google.maps.event.addListener(this._streetView, 'visible_changed', dojo.hitch(this, this._streetViewVisibilityChangeHandler));
}
} else {
this._svMoved = true;
}
}
if (!this._rotateMoved) {
var ob = dojo.query('.gmnoprint img[src*="rotate"]', this._gmapDiv);
if (ob.length > 0) {
dojo.forEach(ob, function(s, idx) {
dojo.place(s.parentNode.parentNode, this._controlDiv);
dojo.style(s, 'position', 'absolute');
dojo.style(s, 'left', '20px');
}, this);
this._rotateMoved = true;
}
}
if (this._rotateMoved && this._svMoved) {
dojo.disconnect(this._mvHandle);
this._mvHandle = null;
}
}
}
},
_streetViewVisibilityChangeHandler: function() {
//console.log('_streetViewVisibilityChangeHandler');
if (this._streetView) {
var vis = this._streetView.getVisible();
this._toggleEsriControl(vis);
this.onStreetViewVisibilityChange(vis);
}
},
// when oblique is shown, we should disable esri mouse events because the projection changes.
_mapTiltChangeHandler: function() {
//console.log('_mapTiltChangeHandler');
var t = this._gmap.getTilt();
if (t == 45) {
//this._toggleEsriControl(true);
//this._map._isPanGMaps = this._map.isPan;
//this._map.disablePan();
dojo.place(this._gmapDiv, this._topDiv);
this._map.disableMapNavigation();
} else if (t == 0) {
//this._toggleEsriControl(false);
//if (this._map._isPanGMaps) this._map.enablePan();
dojo.place(this._gmapDiv, this._element);
this._map.enableMapNavigation();
}
},
_toggleEsriControl: function(turnOff) {
if (turnOff) {
this._isZoomSliderDefault = this._map.isZoomSlider;
this._map.hideZoomSlider();
// gmaps (as of v3.6) still dispatch events even street view is visible. so we disable it here.
this._map.disableMapNavigation();
} else {
if (this._isZoomSliderDefault) {
this._map.showZoomSlider();
}
this._map.enableMapNavigation();
}
},
/**
* Fired when Street View visibility changed.
* @name GoogleMapsLayer#onStreetViewVisibilityChange
* @param {boolean} visibility
* @event
*/
onStreetViewVisibilityChange: function(vis) {
// attach events
},
/**
* Fired when Street View visibility changed.
* @name GoogleMapsLayer#onStreetViewVisibilityChange
* @param {boolean} visibility
* @event
*/
onLoad: function(){
},
_esriPointToLatLng: function(pt) {
var ll = esri.geometry.webMercatorToGeographic(pt);
return new google.maps.LatLng(ll.y, ll.x);
},
_esriExtentToLatLngBounds: function(ext) {
var llb = esri.geometry.webMercatorToGeographic(ext);
return new google.maps.LatLngBounds(new google.maps.LatLng(llb.ymin, llb.xmin, true), new google.maps.LatLng(llb.ymax, llb.xmax, true));
},
_latLngBoundsToEsriExtent: function(bounds) {
var ext = new esri.geometry.Extent(bounds.getSouthWest().lng(), bounds.getSouthWest().lat(), bounds.getNorthEast().lng(), bounds.getNorthEast().lat());
return esri.geometry.geographicToWebMercator(ext);
}
});
dojo.mixin(agsjs.layers.GoogleMapsLayer, {
MAP_TYPE_SATELLITE: "satellite",
MAP_TYPE_HYBRID: "hybrid",
MAP_TYPE_ROADMAP: "roadmap",
MAP_TYPE_TERRAIN: "terrain",
MAP_STYLE_GRAY: [{
featureType: 'all',
stylers: [{
saturation: -80
}, {
lightness: 20
}]
}],
MAP_STYLE_LIGHT_GRAY: [{
featureType: 'all',
stylers: [{
saturation: -80
}, {
lightness: 60
}]
}],
MAP_STYLE_NIGHT: [{
featureType: 'all',
stylers: [{
invert_lightness: 'true'
}]
}]
});
// extend Esri gallery
// note: should not use dojo.ready here because it will not run until all outstanding dojo.require resolved.
// that maybe too late to construct a BasemapGallery manually.
require(['esri/dijit/BasemapGallery'], function(BasemapGallery) {
//console.log('has esri.dijit.BasemapGallery');
esri.dijit.BasemapGallery.prototype._original_postMixInProperties = esri.dijit.BasemapGallery.prototype.postMixInProperties;
esri.dijit.BasemapGallery.prototype._original_startup = esri.dijit.BasemapGallery.prototype.startup;
dojo.extend(esri.dijit.BasemapGallery, {
google: null,
_googleLayers: [],
toggleReference: false,
postMixInProperties: function() {
if (!this._OnSelectionChangeListenerExt) {
this._onSelectionChangeListenerExt = dojo.connect(this, 'onSelectionChange', this, this._onSelectionChangeExt)
}
if (this.google != undefined && (this.showArcGISBasemaps || this.basemapsGroup)) {
this.basemaps.push(new esri.dijit.Basemap({
id: 'google_road',
layers: [new esri.dijit.BasemapLayer({
type: 'GoogleMapsRoad'
})],
title: "Google Road",
thumbnailUrl: dojo.moduleUrl("agsjs.dijit", "images/googleroad.png")
}));
this.basemaps.push(new esri.dijit.Basemap({
id: 'google_satellite',
layers: [new esri.dijit.BasemapLayer({
type: 'GoogleMapsSatellite'
})],
title: "Google Satellite",
thumbnailUrl: dojo.moduleUrl("agsjs.dijit", "images/googlesatellite.png")
}));
this.basemaps.push(new esri.dijit.Basemap({
id: 'google_hybrid',
layers: [new esri.dijit.BasemapLayer({
type: 'GoogleMapsHybrid'
})],
title: "Google Hybrid",
thumbnailUrl: dojo.moduleUrl("agsjs.dijit", "images/googlehybrid.png")
}));
}
if (this.loaded) {
this._onLoadExt();
} else {
this._onLoadListenerExt = dojo.connect(this, 'onLoad', this, this._onLoadExt);
}
if (this._original_postMixInProperties) {
this._original_postMixInProperties();
}
//console.log('dom' + this.domNode);
},
startup: function() {
// user have option of not calling startup for custom UI.
// _startupCalled is used to flag if we need refresh UI for toggle reference layer
this._startupCalled = true;
this._original_startup();
// move from _processReferenceLayersExt because domNode may not be available at the time if manual mode.
if (!this._onGalleryClickListenerExt) {
this._onGalleryClickListenerExt = dojo.connect(this.domNode, 'click', this, this._onGalleryClickExt);
}
},
_onLoadExt: function() {
//console.log('inside _onLoad ');
if (this._onLoadListenerExt)
dojo.disconnect(this._onLoadListenerExt);
if (this.toggleReference) {
dojo.forEach(this.basemaps, function(basemap) {
var layers = basemap.getLayers();
if (layers.length) {
this._processReferenceLayersExt(basemap);
} else {
layers.then(dojo.hitch(this, this._processReferenceLayersExt, basemap));
}
}, this);
}
},
_processReferenceLayersExt: function(basemap) {
var layers = basemap.getLayers();
var hasRef = false, vis = true;
dojo.forEach(layers, function(layer) {
if (layer.isReference) {
hasRef = true;
if (layer.visibility === false) {
vis = false;
}
}
});
if (hasRef && this.toggleReference) {
basemap.title += '';
basemap._hasReference = true;
} else {
basemap._hasReference = false;
}
var needsRefresh = 0;
dojo.forEach(this.basemaps, function(b) {
// make sure all basemaps are processed. it is decided by each basemap has a _hasReference property, regardless true or false.
// if there is any basemap not processed, should not refresh.
// if there are at least one ref layers for that basemap, will refresh.
if (b._hasReference == undefined) {
needsRefresh -= this.basemaps.length;
}
if (b._hasReference) {
needsRefresh++;
}
}, this);
if (needsRefresh >= 0) {
if (needsRefresh > 0 && this._startupCalled) {
// if startup is executed before all basemap are processed, need recall it. if not called yet, does not matter.
this.startup();
}
}
},
_onSelectionChangeExt: function() {
var selected = this.getSelected();
var layer;
if (selected) {
if (this._googleLayers.length > 0) {
dojo.forEach(this._googleLayers, function(lay) {
this.map.removeLayer(lay);
}, this);
this._googleLayers.length = 0;
}
dojo.query('input', this.domNode).forEach(function(m) {
m.disabled = true;
});
var layers = selected.getLayers();
dojo.forEach(layers, function(blay) {
if (blay.type && blay.type.indexOf("GoogleMaps") > -1) {
var mtype = agsjs.layers.GoogleMapsLayer.MAP_TYPE_ROADMAP;
if (blay.type == "GoogleMapsSatellite") {
mtype = agsjs.layers.GoogleMapsLayer.MAP_TYPE_SATELLITE;
} else if (blay.type == "GoogleMapsHybrid") {
mtype = agsjs.layers.GoogleMapsLayer.MAP_TYPE_HYBRID;
}
this.google = this.google || {};
this.google.mapOptions = this.google.mapOptions || {};
this.google.mapOptions.mapTypeId = mtype;
layer = new agsjs.layers.GoogleMapsLayer(this.google);
this.map.addLayer(layer, 0);
this._googleLayers.push(layer);
}
}, this);
}
// when the first basemap is selected, UI may already built, but at that time,
// nothing to mark as selected node because first onSelectionChange has not been fired yet.
var hasSelectedNode = this._checkSelectedNode();
if (!hasSelectedNode && this._startupCalled) {
// mark selected basemap in UI.
this.startup();
// this enables checkbox;
this._checkSelectedNode();
}
},
_checkSelectedNode: function() {
var hasSelectedNode = false;
var layers = this.getSelected().getLayers();
dojo.query('.esriBasemapGallerySelectedNode', this.domNode).forEach(function(n) {
hasSelectedNode = true;
dojo.query('input', n).forEach(function(m) {
m.disabled = false;
// jsapi fires onSelectionChange before reference layer updates, (should be a bug) so we can not set vis directly.
//this._setReferenceVis(m.checked);
dojo.forEach(layers, function(blay) {
if (blay.isReference) {
blay.visibility = m.checked;
}
}, this);
}, this);
}, this);
return hasSelectedNode;
},
_onGalleryClickExt: function(evt) {
var t = evt.target;
if (t.tagName.toLowerCase() == 'input') {
this._setReferenceVis(t.checked);
} else if (dojo.hasClass(t.parentNode, 'esriBasemapGalleryLabelContainer')) {
// this allows basemap switch by clicking on label.
t.parentNode.parentNode.firstChild.click();
}
},
_setReferenceVis: function(visible) {
//arcgis API does not associate the actual created esri.layers.Layer and esri.dijit.BasemapLayer
// so have to use undocumented _basemapGalleryLayerType.
dojo.forEach(this.map.layerIds, function(id) {
var layer = this.map.getLayer(id);
if (layer._basemapGalleryLayerType == "reference") {
if (visible) {
layer.show();
} else {
layer.hide();
}
}
}, this);
},
/**
* get array of active google layers. Typically there is only one layer in the array.
* @return {GoogleMapsLayer[]}
*/
getGoogleLayers: function() {
return this._googleLayers;
}
});
});
//});