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 :
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.
Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me..
ReplyDeleteI am a regular follower of your blog. Really very informative post you shared here.
Kindly keep blogging. If anyone wants to become a Front end developer learn from Javascript Training in Chennai .
or Javascript Training in Chennai.
Nowadays JavaScript has tons of job opportunities on various vertical industry. ES6 Training in Chennai