Sunday, June 10, 2012

Linq2ArcObjects basic version

Introduction

Due to lack of time and also because I have no longer ArcGis desktop on my development environment  I made the code public. This library allows the use of Microsoft  Linq to access rows from attribute tables and feature classes.

To-do’s

I did only limited testing on this library, writing some unit tests could help to stabilize the development of future versions.
A more important job to do is the adding of spatial query possibility. Therefor a special keyword like ‘whereSpatial’ must be added to the Linq syntax.  Not a lot of work, you can look how the attribute query is constructed from the query functionality of the ArcObjects library.  To help the parsing, you have to add special attributes like geometry at the class definition for a feature. This will be needed at the table level and also at attribute level to specify which field contains the geometry contents.

Microsoft has a GUI interface for his Linq to SQL implementation. To eliminate the writing of the table classes, a tool written with ArcObjects engine is useful for generating the classes in C# (or VB.NET). Not a hard job for one that has some experience in processing geodatabases. All information needed in the class definitions is available using ArcObjects methods.
Basic knowledge

The code is written in c#, no VB.NET version is available
You need to have knowledge in the use of attributes defined for classes and properties. Attributes are the driving engine within the Linq parsing.

You need to have knowledge of reading and writing of feature classes. The library can be used with every SDE database. I did no check for the use of file GDB , Access database or shape files.
Code access

I used GIT to make the code public, I did not found another solution. As I am new with GIT , I hope all is correctly transferred.
The repository has the name ‘jpenet / Linq2ArcObjects’ and can be found at https://github.com/
 

Have a lot of programming fun,

Johnny Penet

10/6/2012           Lokeren, Belgium

Saturday, June 9, 2012

ARCGIS JavaScript API – BASIC EDITING COMMANDS SECTION 2 – PART V

1.     Introduction

Until now we saw how we can create and update geometries  (points, polylines and polygones) using the edit and draw widgets. Using the save button we transfer the modification to the ArcGIS server which in turn updates the SDE database. Also important is the data entry of attribute data of a feature. I will illustrate how we can use the attribute template widget to create a general attribute editor for features.
The navigation within the tab control and the accordion control inside a tab will be done with the help of custom bindings as defined in the knockoutJS framework. As we illustrated in previous commands, the handling of the GUI components will be kept out of the ArcGIS framework.
2.     Attribute edit

To simplify the attribute editing we can use the attribute inspector of the ArcGIS JavaScript API. This is a dojo widget that covers the full editing of attributes for a feature. Depending on the attribute field type different input editors are activated in the Dojo component.
The attribute inspector is encapsulated in some general edit attribute method added into the GisEditing class.  The only parameter needed for the tool is the list of fields that needs to be updated. In the implementation a general editing application, we called this method with a null value as field list. Using a null value as field list results in adding all attribute fields of the feature. The object ID is always masked out by the attribute inspector. and does not appears on the input form.

editAttributes: function (fieldInfos) {
 try {
  if (currentFeatureLayer == null)
       return;
   currentOperation = "EditAttributes";
   if (fieldInfos == null)
       fieldInfos = [];
   var layerInfos = [{
      'featureLayer': GisOperation.getMap().getLayer(currentFeatureLayer.name),
      'showDeleteButton': false,
      'isEditable': true,
      'fieldInfos': fieldInfos
   }];
   if (attributeInspector == null) {
      var contentHolder = "<div id='" + attributesDiv + "'></div>";
      require(["dojo/dom-construct"], function (domConstruct) {
         domConstruct.place(contentHolder, "attributeDetails", "first");
      });
      attributeInspector = new esri.dijit.AttributeInspector({
        layerInfos: layerInfos
        }, attributesDiv);
      dojo.connect(attributeInspector, "onAttributeChange", function (feature, fieldName,  
          newFieldValue) {
        //store the updates to apply when the save button is clicked
        currentFeatureLayer.getSelectedFeatures()[0].attributes[fieldName] = newFieldValue;
     });
     }
   this.selectFeatures();
 } catch (err) {
     logging.logMessage("E", "editAttributes failed -->" + err.message, "gisEditing");
 }
},

To  include the attribute inspector widget into the web page,  the best implementation I found is using the dom contructor of the dojo library.  I found out that this is the most stable creation of this component into the web page. This result of the display within the tabular control looks like this:
The nice thing is that with the attribute inspector widget , you get a rich input controls which handles the different input data types of the feature.  The ‘onAttributeChange’ event of the attribute inspector is responsible for the update of the modified attributes into the selected feature. The attribute inspector add also support for the usage of attachments. As you can also see, the attribute inspector add multi language support., but as I did with Silverlight I did not add some multi language support. 
3.     Move point

Although we add a separate tool button to move a point, the implementation of the tool is exactly the same as modifying the vertices of a polygon or polyline. In the main view model class the implementation of the edit tools looks like this :
Edit of the vertices:
    this.editExecute = function () {
        editToolbarGeneralViewModel.editCommand(setEditActive);
    };

 Move of a point:
    this.movePointExecute = function () {
        editToolbarGeneralViewModel.editCommand(setEditActive);
    };

 The underlying edit command will handle the different feature types, and in case of a map point you are allow the move of the point with a drag operation.
4.     Save feature

The save action has been split into the three different actions that can be done on a feature layer,  namely create, update and delete. The code below shows that with the current API you can have the full control over editing on feature layers and how the results are save at the ArcGIS server.

saveAll: function () {
  try {
      var updateGraphic;
      if (currentOperation == "EditVertices") {
         var graphicNew = editTool.getCurrentState();
         if (graphicNew.isModified) {
             updateGraphic = currentFeatureLayer.getSelectedFeatures()[0].setGeometry(graphicNew.graphic.geometry);
             currentFeatureLayer.applyEdits(null, [updateGraphic], null, function (addResults, updateResults, deleteResults) {
             var updateCount = updateResults.length;
             }, function (err) {
                 logging.logMessage("E", "saveAll applyEdits failed -->" + err.message, "gisEditing");
             });
         }
         editTool.deactivate();
      }
      else if (currentOperation == "EditAttributes") {
        try {
          attributeInspector.destroy();
          attributeInspector = null;
          updateGraphic = currentFeatureLayer.getSelectedFeatures()[0];
          currentFeatureLayer.applyEdits(null, [updateGraphic], null, function (addResults, updateResults, deleteResults) {
              var updateCount = updateResults.length;
          }, function (err) {
              logging.logMessage("E", "saveAll applyEdits failed -->" + err.message, "gisEditing");
          });
        } catch (err) {
            logging.logMessage("E", "saveAll failed EditAttributes-->" + err.message, "gisEditing");
        }
      }
      else if (currentOperation == "CreateFeature") {
        if (currentGeometry != null) {
           var symbol;
          if (currentFeatureLayer.geometryType == "esriGeometryPolygon") {
               symbol = gisOperation.getSelectionFillSymbol();
          }
          else if (currentFeatureLayer.geometryType == "esriGeometryPolyline") {
               symbol = gisOperation.getSelectionLineSymbol();
          }
          else if (currentFeatureLayer.geometryType == "esriGeometryPoint") {
               symbol = gisOperation.getSelectionMarkerSymbol();
          }
          var addGraphic = new esri.Graphic(currentGeometry, symbol, currentAttributes);
          currentFeatureLayer.applyEdits([addGraphic], null, null, function (addResults, updateResults, deleteResults) {
              var addCount = addResults.length;
              }, function (err) {
                 logging.logMessage("E", "saveAll apply add failed -->" + err.message, "gisEditing");
           });
        }
        gisOperation.getDraw().deactivate();
      }
      else if (currentOperation == "DeleteFeatures") {
        try {
         var deleteFeatures = new Array();
         require(["dojo/_base/array"], function (array) {
           array.forEach(currentFeatureLayer.getSelectedFeatures(), function (feature, ind) {
              deleteFeatures.push(feature);
           });
         });
         currentFeatureLayer.applyEdits(null, null, deleteFeatures, function (addResults, updateResults, deleteResults) {
            var deleteCount = deleteResults.length;
            }, function (err) {
              logging.logMessage("E", "saveAll applyEdits failed -->" + err.message, "gisEditing");
          });
        } catch (err) {
          logging.logMessage("E", "saveAll failed DeleteFeatures -->" + err.message, "gisEditing");
        }
      }
      currentOperation = "";
      currentAttributes = new Array();
      this.clearSelectFeatures();
      gisOperation.getSelectLayer().clear();
   } catch (err) {
       logging.logMessage("E", "saveAll failed -->" + err.message, "gisEditing")
 }
},


In the save there is a lot of asynchronous processing. By hiding this asynchronous functionality, it makes the job easier at the GUI development. It is also important to add extensive error handling to have a trace in case of errors occurred during the save operation.
5.     Undo modifications
As long as you did not a save with the save button, nothing is modified on the ArcGIS server. Simply clear the graphic layer, as it holds the modification or creation. In case of attribute update, destroy the attribute inspector.

6.     Summary

In the edit tools I showed you how you can implement some simple basis editing of feature layers. Editing, updating or delete of feature can be performed in the same way that is done with ArcGIS Desktop. With the attribute inspector you even get a powerful attribute editor. In the future I hope to illustrate how you can create more complex edit tools as already illustrated in the Silverlight ArcGIS framework illustrated in previous documents. The basic idea will be creating basic building blocks of editing tools that can be put together to create more complex edit tools. This approach is not special, probably a lot of you has already built ArcMap extensions with the main purpose to create extended edit tools to simplify the job of GIS editors.
Based on my experience of writing ArcMap extension for enhanced GIS editing, I will later illustrate how some of this more complex editing tools I implemented in the past as ArcMap tools or commands can be written as tools or commands in JavaScript as part of an ArcGIS Application framework.
ESRI inc. did a real good job by putting an ArcGIS API framework in place to encapsulate the REST services for the ArcGIS Server together with the 10.01 editing functionality for feature layers. This gives us as GIS developers an

Thursday, May 24, 2012

ARCGIS JavaScript API –Using Google Streetview with WMS


1.     Introduction


Before writing section 2 of editing, I first did a small experiment of integrating non ESRI GIS data into a  HTML5 application based on the ArcGis JavaScript API, mainly to show how easy this can be done in a structured way.
As an example I will show the usage of the popular Google Streetview in combination with a WMS service that contains addresses from a region in Belgium. The following steps will be walked through :

·         Load WMS service that contains the address contents. When you look in ArcGis Online, a lot of maps are available for the Flanders region. One I was interested is the ‘GDI-Flanders - INSPIRE View Service – Addresses’ map. This is maintained by AGIV, a governmental organization. I am not sure the contents will stay for free, but for now I can use it to implement WMS support.

·         Because I need a spatial query on the WMS layer, I had to implement a query task for WMS layers.

·         Based on the result of the address selected by the spatial query on the address layer, we implement the Google Streetview based on the Google Streetview API.

What we see here is that there are layers coming from different sources, resulting in different spatial references. Lucky, ESRI helps us to transform geometries from one spatial reference to another through geometry services, that will be covered to in this document.

The final result looks like this, based on my home address :

With the WMS address layer from AGIV


2.     WMS Service


In my initial version of the ArcGis application framework there was no support build in for WMS services. In the ArcGis JavaScript API I found only limited support build in for WMS services.

Creation of a WMS service  to  be added to the map can be done in two ways. You can use either only the URL as parameter, or you can use a layer info object with the URL. In case you only use the URL, the ESRI API will use the proxy defined in the application, as is  required for doing editing. I first tried this approach because it requires little parameters. However I got an error when using a local proxy defined as explained in an earlier document. So I moved to the second approach, specifying a layer info data in the XML configuration as showed below.

<Layer title="AGIV adrespunten" serviceType="Wms" visibleInitial="true" expandable="false" showLabel="false" restURL="http://wms.agiv.be/inspire/wms/adressen" icon="../Images/icons/i_streets.png">
        <WmsExtent  xmin="2" ymin="50" xmax="6" ymax="52" spatialReference="4258"></WmsExtent>
        <WmsLayerInfos>
                <LayerInfo name="Adrespos" title = "Address points"> </LayerInfo>
        </WmsLayerInfos>
</Layer>

Implementing this in the configuration component results in only adding a couple of lines in the REST service. The only point of importance is the retrieval of the information needed to fill in the XML configuration for the WMS service. There are a couple of ways to retrieve the needed information. A first procedure you can use is using the “GetCapabilities” method of the rest service. The drawback of this procedure is that you get a large XML with a mess of properties. A second method is the use of a free utility “Gaia”. This utility is very easy to get information like the name or the spatial reference of a WMS layer. At the same time you can also visualize the WMS layers.

Another issue I encountered was during the initializing of the WMS layer with the ArcGis JavaScript API. In my GIS framework the building of the tables was done in the onload event of the layers. However in the case of WMS layers, no onload event is triggered. I should expect that as with other layers, the full data of the layer is available after the onload. A solution could be by doing a GetCapabilities on the WMS service to retrieve more information. This approach was used in the build of the list of selectable layers to get more information of the WMS service.


function _addNewWmsLayer(baseLayer, serviceType, index) {
   try {
            var layerInfos = new Array();
            var layerNames = new Array();
            for (var i = 0; i < baseLayer.LayerInfos.length; i++) {
              var layer = new esri.layers.WMSLayerInfo({ name: baseLayer.LayerInfos[i].Name, title: baseLayer.LayerInfos[i].Title });
              layerInfos.push(layer);
              layerNames.push(baseLayer.LayerInfos[i].Name);
            }
            var resourceInfo = {
                extent: new esri.geometry.Extent(baseLayer.WmsExtent.xmin, baseLayer.WmsExtent.ymin, baseLayer.WmsExtent.xmax, baseLayer.WmsExtent.ymax,
                    { wkid: baseLayer.WmsExtent.spatialReference }),
                layerInfos: layerInfos
            };
            mapLayer = new esri.layers.WMSLayer(baseLayer.RESTURL, { resourceInfo: resourceInfo, visibleLayers: layerNames });
            mapLayer.setImageFormat("png");
            mapLayers.push(mapLayer);
            map.addLayer(mapLayer);
            baseMapLayerInfos.push(new baseMapLayerInfo(baseLayer.RESTURL, mapLayer.id, baseLayer.Title, baseLayer.LayerInfos[0].Name, serviceType));
            verifyInitialisationMap();
        } catch (e) {
            throw new Error("wmslayer " + baseLayer.RESTURL + " initialize process failed. " + e.name + "\n" + e.message);
        }
    }

The only extra information that you can get at this stage is the id of the layer, only after the WMS layer is added to the map. This is wy I only save the layer information after it has been added to the map. Other information as name, spatial reference comes from the configuration.
As you can see, not very exciting for adding  WMS layer information to our map. I did need to take some additional steps in order to have the possibility to make spatial querying the WMS service possible.

if (layerLegendInfo.serviceType == ArcGISServiceType.Wms) {
// Wms file
  urlGet = layerLegendInfo.url;
  viewModel.wmsUrl = layerLegendInfo.url;
  viewModel.wmsSpatialReference = GisOperation.getMap().getLayer(layerLegendInfo.id).spatialReference;
  var data = {
     VERSION: '1.1.1',
     REQUEST: 'GetCapabilities',
     SERVICE: 'WMS'
  };
  $.ajax({
      type: 'GET',
      url: urlGet,
      data: data,
      dataType: "xml",
      success: function (xml) {
          var jsonContents = $.xml2json(xml);
           viewModel.wmsName = jsonContents.Capability.Layer.Layer.Name;
          if (jsonContents.Capability.Layer.Layer.queryable == '1') {
          // Layer is querable and can be used for selection
            for (var j = 0; j < GisOperation.getBaseMapLayerInfos().length; j++) {
               if (GisOperation.getBaseMapLayerInfos()[j].baseMapLayerId == viewModel.wmsName && GisOperation.getBaseMapLayerInfos()[j].serviceType == 5) {
                 selectionInfo = new layerSelectionInfo(GisOperation.getBaseMapLayerInfos()[j].url, GisOperation.getBaseMapLayerInfos()[j].id, GisOperation.getBaseMapLayerInfos()[j].name, null, "", GisOperation.getLayersSelectionInfos().length, null, ArcGISServiceType.Wms);
                 GisOperation.getLayersSelectionInfos().push(selectionInfo);
               }
            }
          }
          terminateLayerInitialize();
      },
      error: function (jqXHR, textStatus, errorThrown) {
          logMessage("E", "error in selectBuild AJAX call ->" + textStatus + "\n" + errorThrown, "layerSelectionModule");
     }
  })
}

The main thing here is that we use the getCapabilities method to extract some additional information about the WMS layer. The most important here is that we use the querable attribute to know if we can query this WMS layer.

3.     WMS query task


Once we added the WMS layer into our map, we can start creating a WMS query task. If you look at the WMS documentation, it looks simple. However there are some problems around the corner. When you click at a location in the map, you will get coordinates based on the spatial reference from the map, and this is based on the first base map layer loaded. But as you saw in the WMS layer definition, the WMS expect us to return the coordinates based on the spatial reference of the WMS layer. So there is the need for doing a projection conversion. Using the ESRI API you will need to call the projection conversion method of a geometry service running on an ArcGis server.

So before we can start implementing a WMS query task, we will first need to add a projection conversion method, I will use therefor the GisGeoProcessing class. It is not an exciting method, but must be done asynchronous.

convertProjection: function (points, toSpacialReference, callback) {
   GisOperation.getGeometryService().project(points, toSpacialReference,
      function (projectedPoints) {
           callback(projectedPoints);
      }, function errorprojected(err) {
            logMessage("E", "projection convertion failed -->" + err.message, "GisGeoProcessing");
            callBack(null);
  });
}

The geometry service has already been initialized during the startup of the application and is available in the GIS framework.
I created a new GIS class ‘GisExtra’ that contains the new methods needed to illustrate the use of WMS queries and Google Streetview. The implementation of the WMS query is mainly the use of a REST service that will return the features from the spatial query.

wmsQuery: function (url, layerName, screenPoint, callBack) {
  var extent = gisOperation.getMap().extent;
  var points = new Array();
  points.push(new esri.geometry.Point(extent.xmin, extent.ymin, extent.spatialReference));
  points.push(new esri.geometry.Point(extent.xmax, extent.ymax, extent.spatialReference));
  GisGeoProcessing.convertProjection(points, viewModel.wmsSpatialReference,
      function (convertedPoints) {
          var extent = new esri.geometry.Extent(convertedPoints[0].x, convertedPoints[0].y, convertedPoints[1].x, convertedPoints[1].y, convertedPoints[0].spatialReference);
          var urlGet = GisOperation.getLayersLegendInfos()[i].url;
          var data = {
               REQUEST: 'GetFeatureInfo',
               SERVICE: 'WMS',
               VERSION: '1.1.1',
               LAYERS: layerName,
               STYLES: '',
               FORMAT: 'image/png',
               BGCOLOR: '0xFFFFFF',
               TRANSPARENT: 'TRUE',
               SRS: 'EPSG:' + extent.spatialReference.wkid.toString(),
               BBOX: extent.xmin.toString() + "," + extent.ymin.toString() + "," + extent.xmax.toString() + "," + extent.ymax.toString(),
               WIDTH: gisOperation.getMap().width.toString(),
               HEIGHT: gisOperation.getMap().height.toString(),
               QUERY_LAYERS: layerName,
               X: screenPoint.x.toString(),
               Y: screenPoint.y.toString()
         };
         $.ajax({
              type: 'GET',
              url: urlGet,
              data: data,
              dataType: "xml",
              success: function (xml) {
                   var jsonContents = $.xml2json(xml);
                     callBack(jsonContents);
                   },
              error: function (jqXHR, textStatus, errorThrown) {
                     logMessage("E", "error in GetFeatureInfo AJAX call ->" + textStatus + "\n" + errorThrown, "GisExtra/wmsQuery");
             }
         });
    });
},

We first start an asynchronous operation for converting the extent coordinate of the map into coordinates that corresponds with the spatial reference of the WMS service. The other information is related to screen information. Apparently, WMS service do a calculation base on the map screen coordinates and screen point coordinates together with the map extent expressed in geo coordinates.
As you can see, simple querying a WMS services results in three asynchronous actions. The purpose of a good framework is hiding this complexity from the interface.

4.     Google Streetview API


After I have written the WMS query task, I am ready to implement the visualization of Streetview for an address selected. To simplify the GUI, I added an extra tab to the on the web page. I called it “Extra”. With the help of DOJO, I will add dynamically the Google output on the page.

With the help from knockoutJS , the command is initiated and I start the focus of the tab control on the tab ‘Extra’. Then we initiate a draw point operation.

streetviewCommand: function () {
     try {
        viewModel.tabIndex(3);
        // Build the contents
        var tabDigit = dijit.byId(tabNames[3]);
        if (dojo.byId("titlestreetviewId") == null) {
           var contentTitle = "<div id='titlestreetviewId'><pan class='extraTitle'>Streetview information, select address</pan></div>";
           require(["dojo/dom-construct"], function (domConstruct) {
                 domConstruct.place(contentTitle, "extraContents", "first")
           });
        }
        // Start gis operations
        GisOperation.getSelectLayer().clear();
        GisOperation.getMeasurement().clearResult();
        GisCommonTasks.setFinishedInfoEvent(handleAddressResults);
        GisCommonTasks.drawGeometry("point")
    } catch (e) {
       logMessage("E", "streetviewCommand", e.Message);
    }
}

The callback of the draw action will bring us to the next step, doing a spatial query on the WMS layer to get detailed information from the address layer.

function handleAddressResults(geometry) {
   try {
     GisCommonTasks.setFinishedInfoEvent(null);
     if (geometry != null) {
         if (geometry.type == "point") {
            // Execute spatial query
            wmsPoint = geometry;
            var screenPoint = GisOperation.getMap().toScreen(geometry);
            GisExtra.wmsQuery(viewModel.wmsUrl, viewModel.wmsName, screenPoint, wmsQueryComplete);
         }
     }
   } catch (e) {
     logMessage("E", "error in handleAddressResults->" + e.name + "\n" + e.message, "commonToolbarVM");
   }
}

Not much coding needed for the spatial query on the WMS layer. Again the callback method will guide us to the final step, executing the Google API to get the Streetview on the web page. Because Google Maps use WGS-84, we need another projection conversion before we can get to the result. So again an asynchronous operation will be needed. To separate the business logic for the Streetview functionality, I put the method into the newly created GisExtra class.

First I put some extra data coming from the WMS layer on the tab. This is not the correct way in the MVVM model, but I could find another solution, certainly with the Google Streetview coming.

Next we convert the point geometry into the WGS-84 equivalent.
Then we called the Google API.

function wmsQueryComplete(featureInfo) {
   try {
     if (featureInfo.FIELDS) {
         // Add contents to the window
         $("#wmsInfoId").remove();
         $("#streetviewId").remove();
         var contentPosition = "<div id='wmsInfoId'><span>NisCode:" + featureInfo.FIELDS.NISCODE + " CapaKey:" +featureInfo.FIELDS.CAPAKEY + "</span></div>";
         require(["dojo/dom-construct"], function (domConstruct) {
            domConstruct.place(contentPosition, "extraContents", "last")
         });
         var infoTemplate = new esri.InfoTemplate("Agiv Location", "");
         var wmsSymbol = new esri.symbol.SimpleMarkerSymbol().setStyle(esri.symbol.SimpleMarkerSymbol.STYLE_SQUARE).setColor(new dojo.Color([255, 0, 0, 0.5]));
         graphic = new esri.Graphic(wmsPoint, wmsSymbol, {}, infoTemplate);
         GisOperation.getSelectLayer().add(graphic);
     }
     // Interface to google streetview use 4326 (wgs 84)
     GisGeoProcessing.convertProjection([wmsPoint], new esri.SpatialReference({ wkid: 4326 }), function (point) {
         if (point[0] != null) {
          //                    clearTabContents(3);
             if (dojo.byId("streetviewId") == null) {
               var contentPosition = "<div id='streetviewId' style='width: 360px; height: 360px'></div>";
               require(["dojo/dom-construct"], function (domConstruct) {
                  domConstruct.place(contentPosition, "extraContents", "last")
               });
             }
             GisExtra.initStreetView(point[0], dojo.byId("streetviewId"), 50);
          }
     });
 } catch (e) {
    logMessage("E", "wmsQueryComplete", e.Message
 }
}

Again a nice chaining of callback’s guide us to the ultimate action to be done, calling the Google API. As you can see, not a lot of parameters are needed. We need a point in  WGS-84, a div that will contain the image and a radius that will help us solve the facts that the address points need not to be on the street itself but may have an offset to the street, as is the case in the AGIV address locations.
The method creating the image is simple and examples can be found on the Google documentation.
        initStreetView: function (point, divCtrl, radius) { 
            try {
                pointStreetView = point;
                locationGoogle = new google.maps.LatLng(point.y, point.x);
                // Set up the map and enable the Street View control.
                var mapOptions = {
                    center: locationGoogle,
                    zoom: 16,
                    mapTypeId: google.maps.MapTypeId.ROADMAP
                };
                var mapGoogle = new google.maps.Map(divCtrl, mapOptions);
                var panorama = mapGoogle.getStreetView();
                // Set up Street View and initially set it visible. Register the
                var panoOptions = {
                    position: locationGoogle,
                    visible: true
                };
                panorama.setOptions(panoOptions);
                // Create a StreetViewService object.
                var streetviewService = new google.maps.StreetViewService();
                // Compute the nearest panorama to the Google Sydney office
                // using the service and store that pano ID
                streetviewService.getPanoramaByLocation(locationGoogle, radius,
                    function (result, status) {
                        if (status == google.maps.StreetViewStatus.OK) {
                            // We'll monitor the links_changed event to check if the current
                            // pano is either a custom pano or our entry pano.
                            google.maps.event.addListener(panorama, 'links_changed',
                              function () {
                              });
                        }
                    });
            } catch (e) {
                    logMessage("E", "error in Google streetview API interface ->" + err.message, "GisExtra/initStreetView");
            }
        }

The great thing here is that you can create almost the same streetview functionality as that Google Maps expose, maybe not at the same speed. You can explore the street. I wonder if it is not possible to synchronize the Google window with the ArcGis Map. What I show here has certain usage restriction, keeps this in mind.

The biggest job was supporting WMS layers and query functionality. This tokes the most of my time. But after all not so much code is required to support Google Streetview and WMS services. You can still add a lot more functionality to WMS services, but who wonders, maybe one day they will be added to the ArcGis JavaScript API.

Walking into my street from a ArcGis JavaScript Application looks like.