This library allows the developers to use ESRI ArcGIS server v10 in Google Maps JavaScript API v3. The library was ported from an earlier version that developed against ArcGIS Server 9.3 and Google API v2.
There are substaintial changes from Google API from v2 to v3, not all features in v2 are ported to v3, including some related to projection support. Although this library still supports non-mercator projection, it is recommended that new map caches built using Google/Bing scheme in order to get more stable and reliable behavior.
Map | Data/Query | Other Services | Advanced |
---|---|---|---|
|
The first step is to include arcgislink.js
or arcgislink_compiled.js
in your document header, after the Google Maps API has loaded. You can use the
hosted release version if you do not want to download the script.
<script src="/path/to/argislink_compiled.js" type="text/javascript"></script>
Cached map service may provide better response time because it's pre-rendered as image tiles. If the map service is tiled using the same tiling scheme as Google Maps (WebMercator in ArcGIS), it can be added as a standalone map type directly. The following example constructs a new map with only an ArcGIS map type and no Google map types.
var url = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer'; var agsType = new gmaps.ags.MapType(url,{name:'ArcGIS'}); map.mapTypes.set('arcgis', agsType); map.setMapTypeId('arcgis');
View example | Compiled Lib | Compiled All | Phone Size | Back to top
ArcGIS Server provides map services to generate map images from spatial data on the fly,
also known as "dynamic map". Dynamic services are implemented as
MapOverlay,
a subclass of google.maps.OverlayView
, and can be
added with overlay.setMap(map)
. This class is similar
to the google.maps.GroundOverlay
in the core API, as it essentially draws a
single image over the map. However, it refreshes the image as bounds changed. Any map service can be added as an MapOverlay,
regardless of whether it's tiled or not.
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer'; var dynamap = new gmaps.ags.MapOverlay(url); dynamap.setMap(map);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
In many situations, it's ideal to combined a tiled map as a background, with a dynamic map service that serves operational layers on top of it.
var wmUrl = baseUrl + '/Portland/ESRI_LandBase_WebMercator/MapServer'; var dynaUrl = baseUrl + '/Demographics/ESRI_Census_USA/MapServer'; var agsType = new gmaps.ags.MapType(wmUrl,{name:'ArcGIS'}); map.mapTypes.set('arcgis', agsType); var dynamap = new gmaps.ags.MapOverlay(dynaUrl); dynamap.setMap(map);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
Cached Map Service can also be added as Overlay,
and displays on top of all base map types. It's similar to V2's GTileLayerOverlay
.
var url = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer'; var agsType = new gmaps.ags.MapType(url,{name:'ArcGIS', opacity:0.5}); map.overlayMapTypes.insertAt(0, agsType);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
In certain cases it may be desirable to serve non-Cached map service as a tiled map type (in V3 can be overlay or base type). The benefit is that it does not get full view port refresh for each bound change, although it may make more server requests to server because the map is broken into tiles. In the following example, the US census map service, which not cached, is constructed as map type.
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer'; var agsType = new gmaps.ags.MapType(url,{name:'ArcGIS'}); map.overlayMapTypes.insertAt(0, agsType);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
Virtually all GIS data collected by State or Local Governments in Unite States are stored in one of the State Plane Coordinate Systems. In real-world operations, many map services (both tiled and dynamic) are published using the native coordinate system the data uses.
When adding an TileLayer from a tiled map service, the spatial reference of the map service must be loaded into the internal SpatialReferences collections before it can be used. The library has 4 built-in spatial references: NAD GCS (4269), WGS84 (4326), "Web Mercator" (102113 & 102000). It also supports any spatial references based on Lambert Conformal Conic Projection or Transverse Mercator Projection. They can be added either with one line of code by its Well_known_text format or constructed directly with correct parameters. Please use the WKT value from ESRI documentation. However, if the map services returns WKT, then it's not necessary to add the wkid. Spatial references based on other projections can be used by implementing the SpatialReference interface.
In V3, zoom is removed from google.maps.Projection
interface, as a result, the scale ratio between
adjacent zoom levels must be exactly 2. Also, the tile layer must be constructed after the 'load' event of the map service so the application knows the tile size and scales.
var url = 'http://maps.ci.charlotte.nc.us/ArcGIS/rest/services/GET/BaseMap/MapServer'; var svc = new gmaps.ags.MapService(url); google.maps.event.addListenerOnce(svc, 'load', function() { try { var tileLayer = new gmaps.ags.TileLayer(svc); var agsType = new gmaps.ags.MapType([tileLayer], { name: 'StatePlane' }); var bnds = svc.getInitialBounds(); var myOptions = { zoom: 12, center: bnds.getCenter(), mapTypeId: 'stateplane', mapTypeControlOptions: { mapTypeIds: ['stateplane', google.maps.MapTypeId.ROADMAP] } } var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); map.mapTypes.set('stateplane', agsType); } catch (e) { alert(e); } });
View example | Compiled Lib | Compiled All | Phone Size | Back to top
ArcGISOnline is an online geo-spatial service provided by ESRI, Inc. It is a large online repository of mapping resources. Since Q1 of 2010, all the tiled map services are based on web mercator, which is compatible with Google / Bing maps.
Check Licensing before you use these services.
var services = { 'USA Topo' : ['USA_Topo_Maps'] ,'Streets' : ['World_Street_Map'] ,'World Topo': ['World_Topo_Map'] ,'Imagery':['World_Imagery'] ,'Labeled Imagery': ['World_Imagery','Reference/World_Boundaries_and_Places'] ,'Terrain' : ['World_Terrain_Base'] ,'Labeled Terrain' : ['World_Terrain_Base','Reference/World_Reference_Overlay'] }; var agsIds = []; var agsTypes = []; for (var x in services) { if (services.hasOwnProperty(x)) { agsIds.push(x); var urls = services[x]; for (var i = 0; i < urls.length; i++) { urls[i] = 'http://services.arcgisonline.com/ArcGIS/rest/services/' + urls[i] + '/MapServer'; } agsTypes.push(new gmaps.ags.MapType(urls, {name:x})); } } .... var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); for (var i = 0; i < agsIds.length; i++) { map.mapTypes.set(agsIds[i], agsTypes[i]); }
View example | Compiled Lib | Compiled All | Phone Size | Back to top
In V3 the google.maps.MapType.getTile
method offers greater flexibility to manipulate individual tiles, thus provides
the capability of setting opacity on the fly. Also, the one-many relationship between MapType and TileLayer from V2 is preserved for finer control of tile sets. For example,
Hybrid map type contains an image tile set and a label tile set. Opacity can be controled at tile layer level in addition to MapType level.
The following example uses a
slider
from the V3 util lib to change the opacity of the top map type overlay. It's possible to built animation using historic image tiles with this feature.
var opacity = 0.5; var url = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer'; var agsType = new gmaps.ags.MapType(url, { name: 'ArcGIS', opacity: opacity }); map.overlayMapTypes.insertAt(0, agsType); .... google.maps.event.addListener(opSlider, 'drag', function(evt){ var op = opSlider.left()/range; agsType.setOpacity(op); });
Some browsers has default limitations on how many concurrent connections can
be made to a give host based on URL name, so that when many tiles are being
downloaded, some of them have to wait in the queue. One workaround for this
issue is to create multiple DNS names with numbers as suffix.
There is an option in the gmaps.ags.TileLayer
constructor to allow you
to specify an 'host pattern', for example, "mt[4].google.com" means
to rotate hosts across mt0.google.com, mt1.google.com, mt2.google.com,
mt3.google.com (4 hosts)
var url = 'http://maps.ci.charlotte.nc.us/ArcGIS/rest/services/GET/BaseMapWM/MapServer'; var tileLayer = new gmaps.ags.TileLayer(url, { hosts:'mt[4].ci.charlotte.nc.us' }) var agsType = new gmaps.ags.MapType([tileLayer],{name:'ArcGIS'}); map.mapTypes.set('arcgis', agsType); map.setMapTypeId('arcgis');
ArcGIS Map services support identify operation. The server will return feature behind map for any location. Normally this is done via handling the 'click' event on map, then perform Identify opertion on a MapService. In the following example, identify results from different layers are grouped into different tabs.
var url = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer'; svc = new gmaps.ags.MapService(url); var agsType = new gmaps.ags.MapType([new gmaps.ags.TileLayer(svc)], { opacity: 0.5 }); map.overlayMapTypes.insertAt(0, agsType); google.maps.event.addListener(map, 'click', identify); .. .. svc.identify({ geometry : evt.latLng, tolerance : 3, layerIds : [ 2, 3, 4 ], layerOption : 'all', bounds : map.getBounds(), width: map.getDiv().offsetWidth, height: map.getDiv().offsetHeight, overlayOptions: ovOptions }, function (results, err){ if (err) { alert(err.message + err.details.join('\n')); } else { addResultToMap(results, evt.latLng); } }); ... if (!iw) { iw = new google.maps.InfoWindow({ content: container, position: latlng }); } else { iw.setContent(container); iw.setPosition(latlng); } iw.open(map);
You can draw a subset of a feature layer inside a dynamic map service
(MapOverlay) by setting a
SQL expression as its definition
property. If you want to dynamically get the layer
ids, you should listen to the 'load' event of the underline map service.
You can also set it by passing exportOption property in the MapOverlay
constructor.
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer'; dynamap = new gmaps.ags.MapOverlay(url); // set definition after map service load event. google.maps.event.addListenerOnce(dynamap.getMapService(), 'load', function(){ var service = dynamap.getMapService(); service.getLayer("Coarse Counties").definition = "STATE_NAME='Kansas' and POP2007>25000"; service.getLayer("Detailed Counties").definition = "STATE_NAME='Kansas' and POP2007>25000"; service.getLayer("states").definition = "STATE_NAME='Kansas'"; dynamap.setMap(map); }); // set definitions in constructor, hard coded layer ids. dynamap2 = new gmaps.ags.MapOverlay(url, { exportOptions: { layerIds:[5,4,3], layerOption: 'show', layerDefinitions: { '5': "STATE_NAME='New Mexico'", '4': "STATE_NAME='New Mexico' and POP2007>25000", '3': "STATE_NAME='New Mexico' and POP2007>25000" } } }); dynamap2.setMap(map);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
You can control visibility of a layer inside a dynamic map service
(MapOverlay)
by setting its visible
property.
function init(){ .... var toc = ''; for (var i = 0; i < service.layers.length; i++) { toc += '<input type="checkbox" id="layer' + service.layers[i].id + '"'; if (service.layers[i].visible) toc += ' checked="checked"'; toc += ' onclick="setVis()">' + service.layers[i].name + '<br/>'; } document.getElementById('toc').innerHTML = toc; } function setVis() { var service = dynamap.getMapService(); for (var i = 0; i < service.layers.length; i++) { var el = document.getElementById('layer' + service.layers[i].id); service.layers[i].visible = (el.checked === true); } dynamap.refresh(); }
A dynamic map image may take some time to render.
When a intensive server request like map rendering is running in the
background, it's helpful to let the user know about it.
You can display some sort of hourglass animation image or simply a
message such as "loading...".
You can accomplish this by handling different "***start
"
and "***end
" events.
var dynamap = new gmaps.ags.MapOverlay(url);//, { opacity: 0.5 }); google.maps.event.addListener(dynamap, 'drawstart', function(){ document.getElementById('drawing').style.visibility = 'visible'; }); google.maps.event.addListener(dynamap, 'drawend', function(){ document.getElementById('drawing').style.visibility = 'hidden'; }); dynamap.setMap(map);
View example | Compiled Lib | Compiled All | Phone Size | Back to top
You can query a layer with a SQL statement and spatial filters. The result can be displayed as overlays, like markers or polys. You can also attach mouse events to those overlays to make the application more interactive.
... var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer'; var layer = new gmaps.ags.Layer(url + '/3'); var params = { returnGeometry: true, where: "STATE_NAME = 'Wyoming'", outFields: ["NAME", "POP2000", "POP2007", "POP00_SQMI", "POP07_SQMI"], overlayOptions: { polygon: style } }; layer.query(params, processResultSet); } function processResultSet(rs) { var fs = rs.features; for (var i = 0, c = fs.length; i < c; i++) { setupFeature(fs[i]); } } function setupFeature(feat) { var a = feat.attributes; var html = "<div id='iwdiv' style='font-size:12px'><b>" + a['NAME'] + "</b><hr/>" + "<b>2000 Population: </b>" + ...; var g = feat.geometry[0];//V3 supports multiple rings, so should have only 1 element var latlng = getSimpleCenter(g); g.setMap(map); google.maps.event.addListener(g, 'mouseover', function() { highlight(g, html, latlng); }); google.maps.event.addListener(g, 'mouseout', function() { g.setOptions(style); if (iw) { iw.close(); } }); google.maps.event.addListener(g, 'click', function() { highlight(g, html, latlng); }); } ...
Find is an operation supported by a map service to query features based on a single String. It will search the attribute columns of the map layers inside the map service and return matching features.
function find(q) { removeOverlays(); if (iw) iw.close(); document.getElementById('results').innerHTML = ''; var exact = document.getElementById('exact').checked; var params = { returnGeometry: true, searchText: q, contains: !exact, layerIds: [0,2], // city, state searchFields: ["CITY_NAME", "NAME", "SYSTEM", "STATE_ABBR", "STATE_NAME"], sr: 4326 }; service.find(params, processFindResults); } function processFindResults(rs) { var fs = rs.results; for (var i = 0, c = fs.length; i < c; i++) { processFindResult(fs[i]); } }
You can get the coordinates of a location by passing itss address
or other descriptions. You can use
GeocodeService's
findAddressCandidates
method. You can dynamically construct
an input form based on the meta data of the GeocodeService such as
'addressFields' property after it's load event.
geocoder = new gmaps.ags.GeocodeService('http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Locators/ESRI_Geocode_USA/GeocodeServer'); ... geocoder.findAddressCandidates({ inputs: { Address: '380 new york st', State: 'CA', City: 'redlands', Zip: '92373' } }, function(results) { if (results.candidates) { for (var i = 0, c = results.candidates.length; i < c; i++) { var marker = createMarker(results.candidates[i]); marker.setMap(map); ... } } }); ... function createMarker(gc) { var html = 'Matched Address:' + gc.address; var latlng = gc.location; var marker = new google.maps.Marker({ title: gc.address, position: latlng }); google.maps.event.addListener(marker, 'click', function() { iw.setContent(html); iw.setPosition(latlng); iw.open(map); }); return marker; }
You can also find the address of a given geographic coordinates
(reverse geocode), using
GeocodeService's
reverseGeocode
method. In the following example, when user clicked map
or dragged marker, the location is passed into a reverse geocode operation for
address.
geocoder = new gmaps.ags.GeocodeService('http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Locators/ESRI_Geocode_USA/GeocodeServer'); ... google.maps.event.addListener(map, 'click', function(evt) { reverseGeocode(evt.latLng); }); google.maps.event.addListener(marker, 'dragend', function(evt) { reverseGeocode(evt.latLng); }); ... function reverseGeocode(latlng) { if (latlng) { var params = { location: latlng, distance: 100 } geocoder.reverseGeocode(params, function(result) { if (result.address) { var html = ''; var attrs = result.address; for (var x in attrs) { if (attrs.hasOwnProperty(x)) { html += x + ': ' + attrs[x] + '<br/>'; } } marker.setPosition(result.location); iw.setContent(html); iw.open(map); } else { alert('can not find address for point:' + latlng.toString()); } }); } }
You can transform coordinates from and to different coordinate systems using
GeometryService's project
method.
Note the input result geometry may not be a Google API overlay such as Marker, Polyline or Polygon, in that case, ERSI's Geometry format is expected.
In the following example, when user clicks on map, the lat lng is converted to an Alaska Alber projection and back.
function projectToLatLng() { var params = { geometries: [pt], geometryType: gmaps.ags.GeometryType.POINT, inSpatialReference: 2964, outSpatialReference: 4326 }; svc.project(params, function(projectResults, err) { if (!err) { //projectResults.geometries is array of array var marker = projectResults.geometries[0][0]; var latlng = marker.getPosition(); alert(latlng.toString()); } else { alert(err.message + err.details.join(',')); } }); } function projectTo(latlng) { // NAD_1927_Alaska_Albers_Feet: if (latlng) { var params = { geometries: [latlng], outSpatialReference: 2964, inSpatialReference: 4326 } svc.project(params, function(results, err) { if (!err) { pt = results.geometries[0]; var html = latlng.toString()+'<br/><b>NAD_1927_Alaska_Albers</b> x: ' + pt.x + ',<br/> y: ' + pt.y; html += '<br><input type="button" value="Back to LatLong" onclick="projectToLatLng();" />'; iw.setPosition(latlng); iw.setContent(html); iw.open(map); } else { alert(err.message + err.details.join(',')); } }); } }
View example | Compiled Lib | Compiled All | Phone Size | Back to top
You can buffer a geometry
GeometryService's buffer
method.
In the following example, when user clicks on map to draw a polygon, 2 buffers are added.
When the markers are dragged or deleted, the buffers are updated.
function doBuffer(){ for (var i = 0; i< buffers.length; i++) { buffers[i].setMap(null); } buffers.length = 0; var params = { geometries: [poly], bufferSpatialReference:gmaps.ags.SpatialReference.WEB_MERCATOR, distances: [500,1000], unit: 9001, unionResults: true }; svc.buffer(params, function(results, err) { if (!err) { var g; for (var i = 0, I = results.geometries.length; i < I; i++) { for (var j = 0, J = results.geometries[i].length; j < J; j++) { g = results.geometries[i][j]; g.setMap(map); buffers.push (g); } } } else { alert(err.message + err.details.join(',')); } }); }
View example | Compiled Lib | Compiled All | Phone Size | Back to top
You can publish GeoProcessing Model and execute from web client via
GPTask's execute
method.
In the following example, when user clicks on map, a GeoProcessing model will return polygons of areas covered by 1,2,3 minutes of driving time.
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Network/ESRI_DriveTime_US/GPServer/CreateDriveTimePolygons'; ask = new gmaps.ags.GPTask(url); ... function calcArea(pt) { clear(); var params = { 'Input_Location': { features: [{ geometry: pt }], spatialReference: gmaps.ags.SpatialReference.WGS84 }, 'Drive_Times': '1 3 5' }; task.execute({ parameters: params }, function(gpres, err) { if (!err) { var res, loc, f, g; for (var i = 0; i < gpres.results.length; i++) { res = gpres.results[i]; for (var j = 0, J = res.value.features.length; j < J; j++) { f = res.value.features[j]; if (f.geometry) { for (var k = 0, K = f.geometry.length; k < K; k++) { g = f.geometry[k]; polys.push(g); g.setMap(map); g.setOptions({ fillColor: colors[polys.length % colors.length] }) } } } } } else { alert(err.message + err.details.join(',')); } });
ArcGIS server can perform routing with complex parameters, for example, with barriers to avoid. The
RouteTask's solve
method can be used to calculate the routes.
In the following example, the user can click on map to add some stops and barriers, drag them to adjust location, and observe different results when barriers change.
function route() { gmaps.ags.Util.removeFromMap(routes); routes = []; task.solve({ stops: stops, barriers: barriers, findBestSequence: document.getElementById('optimize').checked, overlayOptions: { strokeColor: '#0000BB', strokeWeight: 8, strokeOpacity: 0.5 } }, processResults, handleErr); } function processResults(results) { if (results.routes) { var r = results.routes.features; for (var i = 0, I = r.length; i < I; i++) { gmaps.ags.Util.addToMap(map, r[i].geometry); routes = routes.concat(r[i].geometry); } } }
The following example combines several tools together. It loads a map service, add it as an overlay. There is a table of contents (TOC) that lists layers, and gray out them if not visible at current scale. The map click is also wired to identify operation with layer options (top most, all etc).
var map, ov, iw, marker; function init() { var myOptions = { zoom: 4, center: new google.maps.LatLng(40, -100), mapTypeId: google.maps.MapTypeId.ROADMAP, draggableCursor: 'default' } map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); loadMapService(); google.maps.event.addListener(map, 'click', function(e) { doIdentify(e.latLng); }); google.maps.event.addListener(map, 'zoom_changed', updateTOC); } function loadMapService() { ... ov = new gmaps.ags.MapOverlay(url, { opacity: document.getElementById('op').value }); google.maps.event.addListener(ov.getMapService(), 'load', function() { ... }); } function updateTOC() { ... } function setLayerVisibility() { ... ov.refresh(); } function doIdentify(location) { .. } function processIdentifyResults(res, ll) { .. }
Google released a JavaScript compiler named Closure Compiler. One of the most important feature is "deadcode removal" in advanced mode. It basically will not include or compress the code that it not been used in the application. This library is written in a syntax format that can take advantage of that feature. To use that feature, download compiler, then include "arcgislink_code.js" and your application code as input, and "arcgis_extern.js" and a downloaded copy of Google Maps V3 extern as external file (for JSON stuff).
java -jar compiler.jar --externs arcgis_externs.js --externs google_maps_api_v3.js --output_wrapper "(function(){%output%})()" --compilation_level ADVANCED_OPTIMIZATIONS --js arcgis_code.js --js myapp.js --js_output_file myapp_compiled.js
After compilation, reference the compiled javascript WITHOUT the library because the used code in the lib is included in the compiled code. The following is a list of samples with fully compiled code:
Cached Map | Overlay | Combined | Cached Overlay | Non-Cached MapType | State Plane | ArcGISOnline | Rotating Hosts | Identify | Layer Def | Layer TOC | Hourglass | Layer Query | Find | Geocode | ReverseGeocode | Project | Buffer | Driving Time | Routing | Generic | Back to top