none
Using SpatialDataService of bing map to found boundary of given locations collection RRS feed

  • Question

  • We use SpatialDataService of bing map to found boundary of given locations collection.

    We have 2000 locations in collection for get the boundary of locations. but it take more than 2 minutes to process all locations.

    You have any other method or solution to get boundary in less time. We write sample code as below:

    var geoDataRequestOptions = {
                entityType: region,
                getAllPolygons: true
            };

            //get boundary of locations collection
            Microsoft.Maps.loadModule('Microsoft.Maps.SpatialDataService', function () {
                
                Microsoft.Maps.SpatialDataService.GeoDataAPIManager.getBoundary(locationsCollection, geoDataRequestOptions, map, function (data) {             
                    if (data.results && data.results.length > 0) {
                        map.entities.push(data.results);
                    }       
                });
            });

    Tuesday, May 30, 2017 2:20 PM

Answers

  • That will generate 2000 requests. The GeoData API requires a single request per location. The GeoDataAPIManager in Bing Maps V8 is a helper class with lets you pass in an array of locations to save you having to loop through and create 2000 requests yourself. This is will always be slow and as I said, will generate a lot of billable transactions against your account. 

    If you simply want US postal codes, I recommend using the US Census zip code tabulation data source as that Query API is able to return up to 250 locations per request.

    Are you always loading the same boundaries? If so, don't use the GeoData API, take the time to create your own data source (recommend a GeoJSON file) and use that in your app. It will be way faster than constantly querying all the locations.


    [Blog] [twitter] [LinkedIn]

    Friday, June 2, 2017 5:03 AM
  • The main limitation is that browsers limit the number of concurrent requests to a single domain, believe it is 6 or 8 requests at a time. When processing 2000 requests, the first 6 will process, then when one of them is complete, the next one out of the 2000 will process and so on until it is done. 

    A couple of options for optimization:

    - Create a web service and make the requests in parallel server side. This will be faster, although would likely still take around 30 seconds.

    - Create 4 web workers and call the GeoData API REST service directly. Split your data between the 4 web workers evenly. I believe that for the moment browsers limit the concurrent requests per web worker, thus you are able to process the data about 3 times faster (web workers have a bit of overhead, thus why it isn't 4 times faster). I have searched high and low on if this is expected in browsers but haven't been able to get a straight answer, but it works great now. Note, I wouldn't create more than 4 web workers as usually performance degrades at that point. 

    The US Census data sources are part of the Bing Spatial Data Services. The Bing Spatial Data Services are very easy to use in V8. Here is a code sample which uses the state level data with V8: https://www.bing.com/api/maps/sdkrelease/mapcontrol/isdk#sdsChoroplethMap+JS

    Here is the documentation for the US Census data sources: https://msdn.microsoft.com/en-us/library/mt805047.aspx


    [Blog] [twitter] [LinkedIn]

    Friday, June 2, 2017 10:07 PM
  • I don't have an example that uses the GeoData API with web workers but do have one that uses the REST geocoding service. It would be fairly easily to modify this. Here is the code:

    BatchGeocodeWorker.js

    /*
    * This is a web worker that geocodes a bunch of data in a seperate thread to prevent the page from freezing.
    */
    
    //Provide a handler for the worker message.
    self.onmessage = processWorkerMessage;
    
    self.numRecordsGeocoded = 0;
    self.geocodedData = [];
    self.batchStep = 0;
    self.isGeocoding = false;
    
    self.request = null;
    
    /**
     * Processes the message for the web Worker.
     * @param message The message containing the instructions and data for the web worker. 
     */
    function processWorkerMessage(message) {
        if (self.isGeocoding) {
            self.postMessage({ error: 'Already geocoding data.' });
            return;
        }
    
        if (!message.data.data) {
            self.postMessage({ error: 'No data to geocode.' });
            return;
        }
    
        if (!message.data.sessionKey) {
            self.postMessage({ error: 'No session key provided.' });
        }
    
        self.request = message.data;
    
        self.isGeocoding = true;
    
        if (!self.request.batchSize || self.request.batchSize < 1) {
            self.request.batchSize = 20;
        }
            
        geocodeData();
    };
    
    function geocodeData() {
        var geocodeCnt = self.batchStep * self.request.batchSize;
    
        if (geocodeCnt < self.request.data.length) {
            var len = Math.min(geocodeCnt + self.request.batchSize, self.request.data.length);
    
            for (var i = geocodeCnt; i < len; i++) {
                var geocodeRequest = "https://dev.virtualearth.net/REST/v1/Locations?locality=" + encodeURI(self.request.data[i].city) +
                    "&postalCode=" + encodeURI(self.request.data[i].pc) +
                    "&adminDistrict=" + encodeURI(self.request.data[i].state) +
                    "&countryRegion=" + encodeURI(self.request.data[i].country) +
                    "&maxResults=1&output=json&jsonp=handleGeocodeResponse&key=" + self.request.sessionKey;
    
                //Since we are in a web worker, use import scripts to make JSONP request as we have no DOM.
                importScripts(geocodeRequest);
            }
        }    
    }
    
    function handleGeocodeResponse(r) {
        //Add the first result from the geocoder into the array.
        if (r &&
            r.resourceSets &&
            r.resourceSets.length > 0 &&
            r.resourceSets[0].resources &&
            r.resourceSets[0].resources.length > 0) {
            self.geocodedData.push(r.resourceSets[0].resources[0]);
        } else {
            //Add something if unable to geocode an entry.
            self.geocodedData.push(null);
        }
        
        self.numRecordsGeocoded++;
    
        if (self.numRecordsGeocoded % self.request.batchSize == 0 ||
            self.numRecordsGeocoded == self.request.data.length) {
            //Send the geocoded data back to the map.
            self.postMessage({
                geocodedData: self.geocodedData
            });
    
            //Empty our array of geocoded data.
            self.geocodedData = [];
    
            self.batchStep++;
            geocodeData();
        }
    }
    
    //Format of the message object
    //message.data = {
    //    sessionKey: '',
    //    batchSize: 500,
    //    data: ['']
    //};
    
    
    //Format of result object
    //var result = {
    //    geocodedData: [RESTLocationObject],
    //    error: ''
    //}

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
        <script type='text/javascript' src='http://www.bing.com/mapspreview/MapControl?callback=GetMap' async defer></script>
        <script type='text/javascript' src='TestData2.js'></script>
    
        <script type="text/javascript">
            var map, sessionKey, worker;
    
            var numberOfRenders = 1; //Number of times to perform a render operation while geocoding. If set to 5 and there are 500 records to geocode. The map will render after each group of 100 pushpins have been geocoded.
            var workerGeocodeBatchSize = 25; //The number of locations to geocode as a batch inside of a worker.
            var numberOfWorkers = 8; //The number of workers to spawn to split the work up.
            var numRecordsGeocoded = 0; //Total number of records geocoded.
            var pushpinQueue = []; //Array of pushpin waiting to be added to the map.        
            
            var workers = []; //Array of workers to keep track of.
    
            var startTime; //Time when job was started.
    
            var progressIndicator, outputTbx;
            var testDataSize;
    
            function GetMap() {
                map = new Microsoft.Maps.Map('#mapDiv', {
                    credentials: 'Ah_C8OJJu8wnNX50rGHf8_OYKonuhZ-CfLQ-kXS-4tI-QsTN9pkLPPfgZgKigwa8',
                    zoom: 3
                });
    
                //Cache DOM elements that we will constantly update.
                progressIndicator = document.getElementById('progressIndicator');
                outputTbx = document.getElementById('outputTbx');
    
                map.getCredentials(function (c) {
                    sessionKey = c;
                });
            }
         
            function GeocodeData() {
                map.entities.clear();
                outputTbx.innerHTML = '';
                numRecordsGeocoded = 0;
                StopGeocoding();
    
                //Grab test data
                var dataSizeDD = document.getElementById("dataSize");
                testDataSize = dataSizeDD.options[dataSizeDD.selectedIndex].value;
    
                //Batch geocode test data
                if (testDataSize == 'all') {
                    testDataSize = USZipCodes.length;
                }else{
                    testDataSize = parseInt(testDataSize);
                }
    
                var testData = USZipCodes.slice(0, testDataSize);
    
                var workerBatchSize = Math.ceil(testDataSize/numberOfWorkers);
                
                var renderBatchSize = Math.floor(testDataSize / numberOfRenders);
    
                document.getElementById('loadingScreen').style.display = '';
    
                startTime = new Date().getTime();
    
                //Spawn multiple worker threads.
                for (var w = 0; w < numberOfWorkers; w++) {
                    var worker = new Worker('BatchGeocodeWorker.js');
    
                    worker.onmessage = function (msg) {
                        numRecordsGeocoded += msg.data.geocodedData.length;
    
                        if (msg.data.geocodedData) {
                            var results = msg.data.geocodedData;
                            var loc, pin;
    
                            for (var i = 0, len = results.length; i < len; i++) {
                                if (results[i]) {
                                    loc = new Microsoft.Maps.Location(results[i].point.coordinates[0], results[i].point.coordinates[1]);
                                    pin = new Microsoft.Maps.Pushpin(loc);
                                    pushpinQueue.push(pin);
                                }
                            }
    
                            if (pushpinQueue.length >= renderBatchSize ||
                                numRecordsGeocoded == testDataSize) {
                                map.entities.push(pushpinQueue);
                            }
                        }
    
                        reportProgress();
                       
                        if (numRecordsGeocoded == testDataSize) {
                            document.getElementById('loadingScreen').style.display = 'none';
                            alert('Batch geocode is complete');
                        }
    
                        if (msg.data.error) {
                            alert(msg.data.error);
                        }
                    };
                    worker.onerror = function (e) {
                        alert(e.message);
                    };
    
                    var request = {
                        batchSize: workerGeocodeBatchSize,
                        sessionKey: sessionKey,
                        data: testData.slice(w * workerBatchSize, Math.min((w + 1) * workerBatchSize, testData.length)),
                        workerId: w
                    };
    
                    worker.postMessage(request);  
    
                    workers.push(worker);
                }
            }
    
            function reportProgress() {
                progressIndicator.style.width = (numRecordsGeocoded / testDataSize)*100 + '%';
    
                if (progressIndicator.style.width == '100%') {
                    var time = (new Date().getTime() - startTime) / 1000;
    
                    outputTbx.innerHTML = 'Geocoded: ' + numRecordsGeocoded + '<br/>Elapsed Time: ' + time + 's<br/>Geocodes/Sec: ' + (numRecordsGeocoded / time);
                }
            }
    
            function StopGeocoding() {
                for (var i = 0; i < workers.length; i++) {
                    workers[i].terminate();
                    workers[i] = null;
                }
    
                workers = [];
            }
        </script>
        <style>
            .mapContainer, #mapDiv {
                ; 
                width:1000px; 
                height:600px;
            }
    
            #loadingScreen{
                ; 
                top:0px;
                left:0px;
                width:1000px; 
                height:600px;
                background-color:rgba(0,0,0,0.5);
            }
    
            .progressBar {
                ; 
                border:1px solid #000;
                top:calc(50% - 10px);
                left:calc(50% - 75px);
                width:150px; 
                height:20px;
            }
    
            #progressIndicator{
                width:0px;
                height:20px;
                background-color:rgba(0, 255, 33, 0.5);
            }
        </style>
    </head>
    <body>
        <div class="mapContainer">
            <div id='mapDiv'></div>
    
            <div id="loadingScreen" style="display:none;">
                <div class="progressBar">
                    <div id="progressIndicator"></div>
                </div>
            </div>
        </div>    
    
        <p>
        
        Test Data Size: 
        <select id="dataSize">
            <option value="500">500</option>
            <option value="1000">1000</option>
            <option value="2500">2500</option>
            <option value="5000" selected="selected">5000</option>
            <option value="10000">10000</option>
            <option value="all">All Data (30,647)</option>
        </select>
        
        <input type="button" value="Geocode Data" onclick="GeocodeData()" />
        <input type="button" value="Stop Geocoding" onclick="StopGeocoding()" /></p>
    
        <p><div id="outputTbx"></div></p>
    </body>
    </html>

    Format of TestData.js

    var USCities = [    
    'Akutan, Alaska',
    'Cold Bay, Alaska',
    'False Pass, Alaska',
    'King Cove, Alaska',
    'Sand Point, Alaska',
    ... //thousands of more entries
    ];


    [Blog] [twitter] [LinkedIn]

    Thursday, June 8, 2017 2:01 AM
  • you won't have access to the Microsoft namespace from a we worked as it is a separate thread. What my code does is use the rest services directly. The geodata API in v8 is simply a wrapper around a rest service. https://msdn.microsoft.com/en-us/library/dn306801.aspx

    [Blog] [twitter] [LinkedIn]

    Friday, June 9, 2017 3:59 PM

All replies

  • What types of boundaries are you trying to retrieve and where? Making 2000 calls to a web service will be slow and very costly as each request after the first 50 will generate a billable transaction.

    [Blog] [twitter] [LinkedIn]

    Tuesday, May 30, 2017 4:57 PM
  • No, we not call multiple request. We just call one request with collection of locations and region type(city and postal code).

    We are trying to retrieve city and postal code boundaries. We have 2000 locations and it take 2 to 3 mins to retrieve boundaries. We write sample code as below:

     var geoDataRequestOptions = {
                entityType: 'PopulatedPlace',
                getAllPolygons: true
            };
            //get boundary of locations collection
            Microsoft.Maps.loadModule('Microsoft.Maps.SpatialDataService', function () {     
                Microsoft.Maps.SpatialDataService.GeoDataAPIManager.getBoundary(locationsCollection, geoDataRequestOptions, map, function (data) {             
                    if (data.results && data.results.length > 0) {
                        map.entities.push(data.results);
                    }       
                });
            });

    Friday, June 2, 2017 3:50 AM
  • That will generate 2000 requests. The GeoData API requires a single request per location. The GeoDataAPIManager in Bing Maps V8 is a helper class with lets you pass in an array of locations to save you having to loop through and create 2000 requests yourself. This is will always be slow and as I said, will generate a lot of billable transactions against your account. 

    If you simply want US postal codes, I recommend using the US Census zip code tabulation data source as that Query API is able to return up to 250 locations per request.

    Are you always loading the same boundaries? If so, don't use the GeoData API, take the time to create your own data source (recommend a GeoJSON file) and use that in your app. It will be way faster than constantly querying all the locations.


    [Blog] [twitter] [LinkedIn]

    Friday, June 2, 2017 5:03 AM
  • Hi Ricky,

    Thank You for your reply and clarification on request number. 

    We don't always use Postal Code. It to up to users. User define the criteria that how he/she would like to get boundaries. It can be Country, County, State, City, Postal Code etc. and then they supply the data and data can be of any country like US, UK, Germany etc. to name few. 

    Is there a way to optimise the time? Billable transaction is not a concern right now, time is. Any other way to fetch the boundaries to optimise loading time?

    US Census zip code tabulation data source - Is this part of Bing Map v8 sdk? If yes, could you please guide us to the same. 

    Friday, June 2, 2017 1:32 PM
  • The main limitation is that browsers limit the number of concurrent requests to a single domain, believe it is 6 or 8 requests at a time. When processing 2000 requests, the first 6 will process, then when one of them is complete, the next one out of the 2000 will process and so on until it is done. 

    A couple of options for optimization:

    - Create a web service and make the requests in parallel server side. This will be faster, although would likely still take around 30 seconds.

    - Create 4 web workers and call the GeoData API REST service directly. Split your data between the 4 web workers evenly. I believe that for the moment browsers limit the concurrent requests per web worker, thus you are able to process the data about 3 times faster (web workers have a bit of overhead, thus why it isn't 4 times faster). I have searched high and low on if this is expected in browsers but haven't been able to get a straight answer, but it works great now. Note, I wouldn't create more than 4 web workers as usually performance degrades at that point. 

    The US Census data sources are part of the Bing Spatial Data Services. The Bing Spatial Data Services are very easy to use in V8. Here is a code sample which uses the state level data with V8: https://www.bing.com/api/maps/sdkrelease/mapcontrol/isdk#sdsChoroplethMap+JS

    Here is the documentation for the US Census data sources: https://msdn.microsoft.com/en-us/library/mt805047.aspx


    [Blog] [twitter] [LinkedIn]

    Friday, June 2, 2017 10:07 PM
  • Hi Ricky,

    Thanks for your reply.

    We are work on application where we use bing map services to get boundary of different regions likes city,state,country.

    As per above reply, You provide two different options to optimise the time to retrieve boundaries and the second option sound good. Create 4 web workers and call the GeoData API REST service directly. If you have any sample code to use web worker with bing map services. so, it will useful to understand the web worker and bing map services.

    Wednesday, June 7, 2017 2:46 PM
  • I don't have an example that uses the GeoData API with web workers but do have one that uses the REST geocoding service. It would be fairly easily to modify this. Here is the code:

    BatchGeocodeWorker.js

    /*
    * This is a web worker that geocodes a bunch of data in a seperate thread to prevent the page from freezing.
    */
    
    //Provide a handler for the worker message.
    self.onmessage = processWorkerMessage;
    
    self.numRecordsGeocoded = 0;
    self.geocodedData = [];
    self.batchStep = 0;
    self.isGeocoding = false;
    
    self.request = null;
    
    /**
     * Processes the message for the web Worker.
     * @param message The message containing the instructions and data for the web worker. 
     */
    function processWorkerMessage(message) {
        if (self.isGeocoding) {
            self.postMessage({ error: 'Already geocoding data.' });
            return;
        }
    
        if (!message.data.data) {
            self.postMessage({ error: 'No data to geocode.' });
            return;
        }
    
        if (!message.data.sessionKey) {
            self.postMessage({ error: 'No session key provided.' });
        }
    
        self.request = message.data;
    
        self.isGeocoding = true;
    
        if (!self.request.batchSize || self.request.batchSize < 1) {
            self.request.batchSize = 20;
        }
            
        geocodeData();
    };
    
    function geocodeData() {
        var geocodeCnt = self.batchStep * self.request.batchSize;
    
        if (geocodeCnt < self.request.data.length) {
            var len = Math.min(geocodeCnt + self.request.batchSize, self.request.data.length);
    
            for (var i = geocodeCnt; i < len; i++) {
                var geocodeRequest = "https://dev.virtualearth.net/REST/v1/Locations?locality=" + encodeURI(self.request.data[i].city) +
                    "&postalCode=" + encodeURI(self.request.data[i].pc) +
                    "&adminDistrict=" + encodeURI(self.request.data[i].state) +
                    "&countryRegion=" + encodeURI(self.request.data[i].country) +
                    "&maxResults=1&output=json&jsonp=handleGeocodeResponse&key=" + self.request.sessionKey;
    
                //Since we are in a web worker, use import scripts to make JSONP request as we have no DOM.
                importScripts(geocodeRequest);
            }
        }    
    }
    
    function handleGeocodeResponse(r) {
        //Add the first result from the geocoder into the array.
        if (r &&
            r.resourceSets &&
            r.resourceSets.length > 0 &&
            r.resourceSets[0].resources &&
            r.resourceSets[0].resources.length > 0) {
            self.geocodedData.push(r.resourceSets[0].resources[0]);
        } else {
            //Add something if unable to geocode an entry.
            self.geocodedData.push(null);
        }
        
        self.numRecordsGeocoded++;
    
        if (self.numRecordsGeocoded % self.request.batchSize == 0 ||
            self.numRecordsGeocoded == self.request.data.length) {
            //Send the geocoded data back to the map.
            self.postMessage({
                geocodedData: self.geocodedData
            });
    
            //Empty our array of geocoded data.
            self.geocodedData = [];
    
            self.batchStep++;
            geocodeData();
        }
    }
    
    //Format of the message object
    //message.data = {
    //    sessionKey: '',
    //    batchSize: 500,
    //    data: ['']
    //};
    
    
    //Format of result object
    //var result = {
    //    geocodedData: [RESTLocationObject],
    //    error: ''
    //}

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
        <script type='text/javascript' src='http://www.bing.com/mapspreview/MapControl?callback=GetMap' async defer></script>
        <script type='text/javascript' src='TestData2.js'></script>
    
        <script type="text/javascript">
            var map, sessionKey, worker;
    
            var numberOfRenders = 1; //Number of times to perform a render operation while geocoding. If set to 5 and there are 500 records to geocode. The map will render after each group of 100 pushpins have been geocoded.
            var workerGeocodeBatchSize = 25; //The number of locations to geocode as a batch inside of a worker.
            var numberOfWorkers = 8; //The number of workers to spawn to split the work up.
            var numRecordsGeocoded = 0; //Total number of records geocoded.
            var pushpinQueue = []; //Array of pushpin waiting to be added to the map.        
            
            var workers = []; //Array of workers to keep track of.
    
            var startTime; //Time when job was started.
    
            var progressIndicator, outputTbx;
            var testDataSize;
    
            function GetMap() {
                map = new Microsoft.Maps.Map('#mapDiv', {
                    credentials: 'Ah_C8OJJu8wnNX50rGHf8_OYKonuhZ-CfLQ-kXS-4tI-QsTN9pkLPPfgZgKigwa8',
                    zoom: 3
                });
    
                //Cache DOM elements that we will constantly update.
                progressIndicator = document.getElementById('progressIndicator');
                outputTbx = document.getElementById('outputTbx');
    
                map.getCredentials(function (c) {
                    sessionKey = c;
                });
            }
         
            function GeocodeData() {
                map.entities.clear();
                outputTbx.innerHTML = '';
                numRecordsGeocoded = 0;
                StopGeocoding();
    
                //Grab test data
                var dataSizeDD = document.getElementById("dataSize");
                testDataSize = dataSizeDD.options[dataSizeDD.selectedIndex].value;
    
                //Batch geocode test data
                if (testDataSize == 'all') {
                    testDataSize = USZipCodes.length;
                }else{
                    testDataSize = parseInt(testDataSize);
                }
    
                var testData = USZipCodes.slice(0, testDataSize);
    
                var workerBatchSize = Math.ceil(testDataSize/numberOfWorkers);
                
                var renderBatchSize = Math.floor(testDataSize / numberOfRenders);
    
                document.getElementById('loadingScreen').style.display = '';
    
                startTime = new Date().getTime();
    
                //Spawn multiple worker threads.
                for (var w = 0; w < numberOfWorkers; w++) {
                    var worker = new Worker('BatchGeocodeWorker.js');
    
                    worker.onmessage = function (msg) {
                        numRecordsGeocoded += msg.data.geocodedData.length;
    
                        if (msg.data.geocodedData) {
                            var results = msg.data.geocodedData;
                            var loc, pin;
    
                            for (var i = 0, len = results.length; i < len; i++) {
                                if (results[i]) {
                                    loc = new Microsoft.Maps.Location(results[i].point.coordinates[0], results[i].point.coordinates[1]);
                                    pin = new Microsoft.Maps.Pushpin(loc);
                                    pushpinQueue.push(pin);
                                }
                            }
    
                            if (pushpinQueue.length >= renderBatchSize ||
                                numRecordsGeocoded == testDataSize) {
                                map.entities.push(pushpinQueue);
                            }
                        }
    
                        reportProgress();
                       
                        if (numRecordsGeocoded == testDataSize) {
                            document.getElementById('loadingScreen').style.display = 'none';
                            alert('Batch geocode is complete');
                        }
    
                        if (msg.data.error) {
                            alert(msg.data.error);
                        }
                    };
                    worker.onerror = function (e) {
                        alert(e.message);
                    };
    
                    var request = {
                        batchSize: workerGeocodeBatchSize,
                        sessionKey: sessionKey,
                        data: testData.slice(w * workerBatchSize, Math.min((w + 1) * workerBatchSize, testData.length)),
                        workerId: w
                    };
    
                    worker.postMessage(request);  
    
                    workers.push(worker);
                }
            }
    
            function reportProgress() {
                progressIndicator.style.width = (numRecordsGeocoded / testDataSize)*100 + '%';
    
                if (progressIndicator.style.width == '100%') {
                    var time = (new Date().getTime() - startTime) / 1000;
    
                    outputTbx.innerHTML = 'Geocoded: ' + numRecordsGeocoded + '<br/>Elapsed Time: ' + time + 's<br/>Geocodes/Sec: ' + (numRecordsGeocoded / time);
                }
            }
    
            function StopGeocoding() {
                for (var i = 0; i < workers.length; i++) {
                    workers[i].terminate();
                    workers[i] = null;
                }
    
                workers = [];
            }
        </script>
        <style>
            .mapContainer, #mapDiv {
                ; 
                width:1000px; 
                height:600px;
            }
    
            #loadingScreen{
                ; 
                top:0px;
                left:0px;
                width:1000px; 
                height:600px;
                background-color:rgba(0,0,0,0.5);
            }
    
            .progressBar {
                ; 
                border:1px solid #000;
                top:calc(50% - 10px);
                left:calc(50% - 75px);
                width:150px; 
                height:20px;
            }
    
            #progressIndicator{
                width:0px;
                height:20px;
                background-color:rgba(0, 255, 33, 0.5);
            }
        </style>
    </head>
    <body>
        <div class="mapContainer">
            <div id='mapDiv'></div>
    
            <div id="loadingScreen" style="display:none;">
                <div class="progressBar">
                    <div id="progressIndicator"></div>
                </div>
            </div>
        </div>    
    
        <p>
        
        Test Data Size: 
        <select id="dataSize">
            <option value="500">500</option>
            <option value="1000">1000</option>
            <option value="2500">2500</option>
            <option value="5000" selected="selected">5000</option>
            <option value="10000">10000</option>
            <option value="all">All Data (30,647)</option>
        </select>
        
        <input type="button" value="Geocode Data" onclick="GeocodeData()" />
        <input type="button" value="Stop Geocoding" onclick="StopGeocoding()" /></p>
    
        <p><div id="outputTbx"></div></p>
    </body>
    </html>

    Format of TestData.js

    var USCities = [    
    'Akutan, Alaska',
    'Cold Bay, Alaska',
    'False Pass, Alaska',
    'King Cove, Alaska',
    'Sand Point, Alaska',
    ... //thousands of more entries
    ];


    [Blog] [twitter] [LinkedIn]

    Thursday, June 8, 2017 2:01 AM
  • Hi Ricky,

    We use Bing map V8 and we have gone through above sample code and tried to implement our web worker logic for GeoData API. We have tested your given sample code and it is working properly with REST geocoding service. But, when we implement this with Bing map V8, we unable to get Microsoft.Maps object inside BatchGeocodeWorker.js file. It give error like “Microsoft is undefined”. How can we resolve this error.

    Can we access and use Microsoft and Bing Map services inside BatchGeocodeWorker.js. Please find the modified sample code in green color as below:

    1.BatchGeocodeWorker.js

    /*
    * This is a web worker that geocodes a bunch of data in a seperate thread to prevent the page from freezing.
    */

    //Provide a handler for the worker message.
    self.onmessage = processWorkerMessage;

    self.numRecordsGeocoded = 0;
    self.geocodedData = [];
    self.batchStep = 0;
    self.isGeocoding = false;

    self.request = null;

    /**
     * Processes the message for the web Worker.
     * @param message The message containing the instructions and data for the web worker. 
     */
    function processWorkerMessage(message) {

        if (self.isGeocoding) {
            self.postMessage({ error: 'Already geocoding data.' });
            return;
        }

        if (!message.data.data) {
            self.postMessage({ error: 'No data to geocode.' });
            return;
        }

        if (!message.data.sessionKey) {
            self.postMessage({ error: 'No session key provided.' });
        }

        self.request = message.data;

        self.isGeocoding = true;

        if (!self.request.batchSize || self.request.batchSize < 1) {
            self.request.batchSize = 20;
        }

        geocodeData();
    };

    function geocodeData() {
        var geocodeCnt = self.batchStep * self.request.batchSize;

        if (geocodeCnt < self.request.data.length) {
            var len = Math.min(geocodeCnt + self.request.batchSize, self.request.data.length);

            for (var i = geocodeCnt; i < len; i++) {

                var searchManager = new Microsoft.Maps.Search.SearchManager(map);
                var requestOptions = {
                    bounds: map.getBounds(),
                    where: self.request.data[i],
                    callback: function (answer, userData) {
                        handleGeocodeResponse(answer.results[0]);
                    }
                };
                searchManager.geocode(requestOptions);

            }
        }
    }

    function handleGeocodeResponse(r) {
        //Add the first result from the geocoder into the array.
        if (r) {
            self.geocodedData.push(r.resourceSets[0].resources[0]);
        } else {
            //Add something if unable to geocode an entry.
            self.geocodedData.push(null);
        }

        self.numRecordsGeocoded++;

        if (self.numRecordsGeocoded % self.request.batchSize == 0 ||
            self.numRecordsGeocoded == self.request.data.length) {
            //Send the geocoded data back to the map.
            self.postMessage({
                geocodedData: self.geocodedData
            });

            //Empty our array of geocoded data.
            self.geocodedData = [];

            self.batchStep++;
            geocodeData();
        }
    }

    2.index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
        <script type='text/javascript' src='http://www.bing.com/mapspreview/MapControl?callback=GetMap' async defer></script>
        <script type='text/javascript' src='TestData2.js'></script>
        <script src="BatchGeocodeWorker.js"></script>
        <script type="text/javascript">
            var map, sessionKey, worker;

            var numberOfRenders = 1; //Number of times to perform a render operation while geocoding. If set to 5 and there are 500 records to geocode. The map will render after each group of 100 pushpins have been geocoded.
            var workerGeocodeBatchSize = 25; //The number of locations to geocode as a batch inside of a worker.
            var numberOfWorkers = 2; //The number of workers to spawn to split the work up.
            var numRecordsGeocoded = 0; //Total number of records geocoded.
            var pushpinQueue = []; //Array of pushpin waiting to be added to the map.

            var workers = []; //Array of workers to keep track of.

            var startTime; //Time when job was started.

            var progressIndicator, outputTbx;
            var testDataSize;

            function GetMap() {

                map = new Microsoft.Maps.Map('#mapDiv', {
                    credentials: 'Ah_C8OJJu8wnNX50rGHf8_OYKonuhZ-CfLQ-kXS-4tI-QsTN9pkLPPfgZgKigwa8',
                    zoom: 3
                });

                Microsoft.Maps.loadModule('Microsoft.Maps.Search', function () {
                    
                });

                //Cache DOM elements that we will constantly update.
                progressIndicator = document.getElementById('progressIndicator');
                outputTbx = document.getElementById('outputTbx');
            }

            function GeocodeData() {
                map.entities.clear();
                outputTbx.innerHTML = '';
                numRecordsGeocoded = 0;
                StopGeocoding();

                //Grab test data
                var dataSizeDD = document.getElementById("dataSize");
                testDataSize = dataSizeDD.options[dataSizeDD.selectedIndex].value;

                //Batch geocode test data
                if (testDataSize == 'all') {
                    testDataSize = USCities.length;
                } else {
                    testDataSize = parseInt(testDataSize);
                }

                var testData = USCities.slice(0, testDataSize);

                var workerBatchSize = Math.ceil(testDataSize / numberOfWorkers);

                var renderBatchSize = Math.floor(testDataSize / numberOfRenders);

                document.getElementById('loadingScreen').style.display = '';

                startTime = new Date().getTime();

                //Spawn multiple worker threads.
                for (var w = 0; w < numberOfWorkers; w++) {

                    var worker = new Worker('BatchGeocodeWorker.js');

                    worker.onmessage = function (msg) {
                        numRecordsGeocoded += msg.data.geocodedData.length;

                        if (msg.data.geocodedData) {
                            var results = msg.data.geocodedData;
                            var loc, pin;

                            for (var i = 0, len = results.length; i < len; i++) {
                                if (results[i]) {
                                    loc = new Microsoft.Maps.Location(results[i].point.coordinates[0], results[i].point.coordinates[1]);
                                    pin = new Microsoft.Maps.Pushpin(loc);
                                    pushpinQueue.push(pin);
                                }
                            }

                            if (pushpinQueue.length >= renderBatchSize ||
                                numRecordsGeocoded == testDataSize) {
                                map.entities.push(pushpinQueue);
                            }
                        }

                        //reportProgress();

                        if (numRecordsGeocoded == testDataSize) {
                            document.getElementById('loadingScreen').style.display = 'none';
                            alert('Batch geocode is complete');
                        }

                        if (msg.data.error) {
                            alert(msg.data.error);
                        }
                    };

                    worker.onerror = function (e) {
                        alert(e.message);
                    };

                    var request = {
                        batchSize: workerGeocodeBatchSize,
                        data: testData,
                        workerId: w        
                    };

                    worker.postMessage(request);

                    workers.push(worker);
                }
            }
            function StopGeocoding() {
                for (var i = 0; i < workers.length; i++) {
                    workers[i].terminate();
                    workers[i] = null;
                }
                workers = [];
            }
        </script>
        <style>
            .mapContainer, #mapDiv {
                ;
                width: 1000px;
                height: 600px;
            }

            #loadingScreen {
                ;
               
               
                width: 1000px;
                height: 600px;
                background-color: rgba(0,0,0,0.5);
            }

            .progressBar {
                ;
                border: 1px solid #000;
               
               
                width: 150px;
                height: 20px;
            }

            #progressIndicator {
                width: 0px;
                height: 20px;
                background-color: rgba(0, 255, 33, 0.5);
            }
        </style>
    </head>
    <body>
        <div class="mapContainer">
            <div id='mapDiv'></div>

            <div id="loadingScreen" style="display:none;">
                <div class="progressBar">
                    <div id="progressIndicator"></div>
                </div>
            </div>
        </div>

        <p>
            Test Data Size:
            <select id="dataSize">
                <!--<option value="1">500</option>
                <option value="1000">1000</option>
                <option value="2500">2500</option>-->
                <option value="2" selected="selected">2</option>
                <option value="4">4</option>
                <option value="all">All</option>
            </select>

            <input type="button" value="Geocode Data" onclick="GeocodeData()" />
            <input type="button" value="Stop Geocoding" onclick="StopGeocoding()" />
        </p>

        <p><div id="outputTbx"></div></p>
    </body>
    </html>

    3.Format of TestData.js

    var USCities = [    
    'Akutan, Alaska',
    'Cold Bay, Alaska',
    'False Pass, Alaska',
    'King Cove, Alaska',
    'Sand Point, Alaska'
    ];


    Friday, June 9, 2017 11:33 AM
  • you won't have access to the Microsoft namespace from a we worked as it is a separate thread. What my code does is use the rest services directly. The geodata API in v8 is simply a wrapper around a rest service. https://msdn.microsoft.com/en-us/library/dn306801.aspx

    [Blog] [twitter] [LinkedIn]

    Friday, June 9, 2017 3:59 PM