Introduction

 

On my previous blog posts, it was shown how to develop the Google Maps PI Coresight custom symbol using the Google Maps JavaScript API. On this blog post, we will develop the Google Street View PI Coresight custom symbol using the same API. As a result, the code from both symbols are very similar since they import the same JavaScript library and use the same API. The main difference is that in this blog post, we are going to work with the StreetViewPanorama class.

 

The final library can generate this great Street View image as shown below:

 

 

 

Getting started using the StreetViewPanorama class

 

There is a great and simple sample on Google documentation about how to use this class. In order to initialize this class, you should instantiate the google.maps.StreetViewPanorama class. Here is an example:

 

function initialize() {
  panorama = new google.maps.StreetViewPanorama(
      document.getElementById('street-view'),
      {
        position: {lat: 37.869260, lng: -122.254811},
        pov: {heading: 165, pitch: 0},
        zoom: 1
      });
}

}

 

Therefore, with this function, we can get the heading, pitch, zoom, latitude and longitude values and then initialize the Google Street View display.

 

If we want to update the Google Street View display, you just need to call the setOptions method from the panorama object. Here is an example:

 

var currentPov = {
    heading: 10,
    pitch: 0,
    zoom: 1
};

panorama.setOptions({
    position: { lat: 41, lng: 21 },
    pov: currentPov
});

 

 

 

This same method also allows you to select some other options for the Street Views Controls:

 

panorama.setOptions({
    position: { lat: 41, lng: 21 },
    pov: currentPov,
    addressControlOptions: {
        position: google.maps.ControlPosition.BOTTOM_CENTER
    },
    linksControl: false,
    panControl: false,
    enableCloseButton: false
});

 

Now, we can start developing our PI Coresight custom symbol.

 

PI Coresight Symbol Code

 

All necessary files can be downloaded from this GitHub repository.

 

If you want to understand better how this symbol works under the hood, I strongly suggest reading my previous blogs about developing the Google Maps PI Coresight custom symbol.

 

Here is what the custom symbol should do:

 

  1. Load the Google Maps JavaScript library in case it is not loaded yet
  2. Create a Street View display with some initial values (not from PI).
  3. When the dataUpdate method is called, it creates a list of indexes in order to find the correct values on the data variable. Please refer to the Google Maps blog posts for more information.
  4. Update the StreetView display with new values retrieved from PI

 

Note that in order to update the display the indexes from scope.config needs to be built (they cannot be -1). This happens in two situations:

  • When the indexes are saved with the display. When the PI Coresight display is loaded, it also loads the indexes.
  • When PI Coresight sends data.Rows with Label and Path properties.

 

Finally, here is the code:

 

Content of sym-gtreetview.js:

 

(function (CS) {
    var definition = {
        typeName: 'gstreetview',
        datasourceBehavior: CS.DatasourceBehaviors.Multiple,
        iconUrl: 'Images/streetview.svg',
        getDefaultConfig: function () {
            return {
                DataShape: 'Table',
                Height: 400,
                Width: 400,
                HeadingName: 'Heading',
                PitchName: 'Pitch',
                ZoomName: 'Zoom',
                LngName: 'Longitude',
                LatName: 'Latitude',
                LatIndex: -1,
                LngIndex: -1,
                HeadingIndex: -1,
                PitchIndex: -1,
                ZoomIndex: -1,
                AddressControl: true,
                ClickToGo: true,
                DisableDefaultUI: false,
                DisableDoubleClickZoom: false,
                ImageDateControl: true,
                PanControl: true,
                ZoomControl: true
            };
        },
        configOptions: function () {
            return [{
                title: 'Format Symbol',
                mode: 'format'
            }];
        },
        init: init
    };






    window.gStreetViewCallback = function () {
        $(window).trigger('gSvLoaded');
    }


    function loadGoogleMaps() {
        if (window.google == undefined) {
            if (window.googleRequested) {
                setTimeout(function () {
                    window.gStreetViewCallback();
                }, 200);


            }
            else {
                var script_tag = document.createElement('script');
                script_tag.setAttribute("type", "text/javascript");
                script_tag.setAttribute("src", "http://maps.google.com/maps/api/js?sensor=false&callback=gStreetViewCallback");
                (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
                window.googleRequested = true;
            }
        }
        else {
            window.gStreetViewCallback();
        }
    }




    function init(scope, elem) {
        var container = elem.find('#container')[0];
        var id = "gstreetview_" + Math.random().toString(36).substr(2, 16);
        container.id = id;
        scope.id = id;






        scope.currentLatLng = { lat: -23.5626384, lng: -46.6510285 };
        scope.startStreetView = function () {
            if (scope.panorama == undefined) {
                scope.panorama = new google.maps.StreetViewPanorama(document.getElementById(scope.id), {
                    position: scope.currentLatLng,
                    pov: {
                        heading: 0,
                        pitch: 0,
                        zoom: 1
                    }
                });
            }
            scope.updateGoogleStreetViewConfig(scope.config);
        };






        scope.updateGoogleStreetViewConfig = function (config) {
            scope.LatIndex = parseInt(config.LatIndex);
            scope.LngIndex = parseInt(config.LngIndex);
            scope.HeadingIndex = parseInt(config.HeadingIndex);
            scope.PitchIndex = parseInt(config.PitchIndex);
            scope.ZoomIndex = parseInt(config.ZoomIndex);


            if (scope.panorama != undefined) {
                scope.panorama.setOptions({
                    addressControl: config.AddressControl,
                    clickToGo: config.ClickToGo,
                    disableDefaultUI: config.DisableDefaultUI,
                    disableDoubleClickZoom: config.DisableDoubleClickZoom,
                    imageDateControl: config.ImageDateControl,
                    panControl: config.PanControl,
                    zoomControl: config.ZoomControl
                });
            }
        };


        scope.resizeGoogleStreetView = function (width, height) {
            if (scope.panorama != undefined) {
                google.maps.event.trigger(scope.panorama, "resize");
            }
        }






        $(window).bind('gSvLoaded', scope.startStreetView);
        loadGoogleMaps();






        scope.updateIndexes = function (data) {
            var isValid = true;
            scope.config.LatIndex = -1;
            scope.config.LngIndex = -1;
            scope.config.HeadingIndex = -1;
            scope.config.PitchIndex = -1;
            scope.config.ZoomIndex = -1;


            for (var i = 0; i < data.Rows.length; i++) {
                var splitResult = data.Rows[i].Label.split('|');
                if (splitResult[1] == scope.config.LatName) {
                    scope.config.LatIndex = i;
                }
                else if (splitResult[1] == scope.config.LngName) {
                    scope.config.LngIndex = i;
                }
                else if (splitResult[1] == scope.config.HeadingName) {
                    scope.config.HeadingIndex = i;
                }
                else if (splitResult[1] == scope.config.PitchName) {
                    scope.config.PitchIndex = i;
                }
                else if (splitResult[1] == scope.config.ZoomName) {
                    scope.config.ZoomIndex = i;
                }
            }


            var checkIndex = function(attributeName, indexValue)
            {
                if (indexValue == -1) {
                    isValid = false;
                    alert(attributeName + ' attribte was not found');
                }
            }


            checkIndex('Latitude', scope.config.LatIndex);
            checkIndex('Longitude', scope.config.LngIndex);
            checkIndex('Zoom', scope.config.ZoomIndex);
            checkIndex('Pitch', scope.config.PitchIndex);
            checkIndex('Heading', scope.config.HeadingIndex);
            return isValid;




        }




        scope.updateDisplay = function (data) {
            var currentPov = {
                heading: parseFloat(data.Rows[scope.config.HeadingIndex].Value),
                pitch: parseFloat(data.Rows[scope.config.PitchIndex].Value),
                zoom: parseFloat(data.Rows[scope.config.ZoomIndex].Value)
            };


            scope.currentLatLng = { lat: parseFloat(data.Rows[scope.config.LatIndex].Value), lng: parseFloat(data.Rows[scope.config.LngIndex].Value) };


            setTimeout(function () {
                scope.panorama.setOptions({
                    position: scope.currentLatLng,
                    pov: currentPov
                });
            }, 3000);


        }


        scope.dataUpdate = function (data) {
            if ((data == null) || (data.Rows.length == 0)) {
                return;
            }


            if (data.Rows[0].Path) {
                scope.updateIndexes(data);
            }


            if (scope.panorama != undefined) {
                scope.updateDisplay(data);
            }
        }


        return { dataUpdate: scope.dataUpdate, resize: scope.resizeGoogleStreetView, configChange: scope.updateGoogleStreetViewConfig };
    }


    CS.symbolCatalog.register(definition);
})(window.Coresight);

 

 

Content of sym-gtreetview-config.html:

 

<div class="c-side-pane t-toolbar">
    <span style="color:#fff; margin-left:15px">Attributes Names Settings</span>
</div>


<div class="c-config-content">
    Latitude attribute name:
    <input type="text" ng-model="config.LatName">
</div>
<div class="c-config-content">
    Longitude attribute name:
    <input type="text" ng-model="config.LngName">
</div>


<div class="c-config-content">
    Heading attribute name:
    <input type="text" ng-model="config.HeadingName">
</div>
<div class="c-config-content">
    Pitch attribute name:
    <input type="text" ng-model="config.PitchName">
</div>


<div class="c-config-content">
    Zoom attribute name:
    <input type="text" ng-model="config.ZoomName">
</div>


<div class="c-side-pane t-toolbar">
    <span style="color:#fff; margin-left:15px">Street View Options</span>
</div>




<div class="c-config-content">
    Address Control:
    <input type="checkbox" ng-model="config.AddressControl">
</div>


<div class="c-config-content">
    Click To Go:
    <input type="checkbox" ng-model="config.ClickToGo">
</div>


<div class="c-config-content">
    Disable Default UI:
    <input type="checkbox" ng-model="config.DisableDefaultUI">
</div>


<div class="c-config-content">
    Disable Double Click Zoom:
    <input type="checkbox" ng-model="config.DisableDoubleClickZoom">
</div>


<div class="c-config-content">
    Image Date Control:
    <input type="checkbox" ng-model="config.ImageDateControl">
</div>




<div class="c-config-content">
    Pan Control:
    <input type="checkbox" ng-model="config.PanControl">
</div>




<div class="c-config-content">
    Zoom Control:
    <input type="checkbox" ng-model="config.ZoomControl">
</div>

 

Content of sym-gtreetview-template.html:

 

<div id="container" style="width:100%;height:calc(100% - 50px);">


</div>
<center>
    <br /><br />
    <p style="color: white;">Right click here to format this symbol</p>
</center>

 

 

 

 

Conclusions

 

I hope you can find this blog post useful and thank you for reading it.

 

It is always good to remember that the PI Coresight Extensiblity used to develop those custom JavaScript symbols is still in CTP.