Stephen Romero
Stephen Romero

Reputation: 3022

Filter though object array based on distance between GPS locations(javascript)

I have an array of location objects with GPS locations tied to the objects. I am wanting to filter through the array based on the users current location and compare the distance between the user location and location objects.

I have a current function that accomplishes this, but I know this is not efficient. I would prefer to filter the array than to create a temp variable and assign the temp variable to the array every time.

//sample data
public getLocation = [
    {
			"ID": "1",
			"Name": "Test Location 1",
			"Lat": "32.16347467",
			"Lon": "-103.67178545"
		}, {
			"ID": "2",
			"Name": "Test Location 2",
			"Lat": "32.16347451",
			"Lon": "-103.67178544"
		}, {
			"ID": "3",
			"Name": "Test Location 3",
			"Lat": "32.13559815",
			"Lon": "-103.67544362"
		}, {
			"ID": "4",
			"Name": "Test Location 4",
			"Lat": "32.40144407",
			"Lon": "-103.13168477"
		}, {
			"ID": "5",
			"Name": "Test Location ",
			"Lat": "32.14557039",
			"Lon": "-103.67011361",
		}
  ]

grabLocation(){
     this.platform.ready().then(() => {
     this.geolocation.getCurrentPosition().then((resp) => {
         this.userLocation = [parseFloat(resp.coords.latitude.toFixed(4)),parseFloat(resp.coords.longitude.toFixed(4))];
         this.userLocation = [resp.coords.latitude,resp.coords.longitude];
         var getLocation
         getLocation = this.asyncLocations.grabUserLoc(this.userLocation[0],this.userLocation[1]);
         console.log(getLocation);
       }).catch((error) => {
         this.presentToast(error);
       });
     });
   }
//asyncLocations.ts
grabUserLoc(lat,lon){
    var tempLocation= [];

    for(let i=0;i<this.getLocation.length;i++){
      if((this.getLocation[i]['Lat']!="") && this.getLocation[i]['Lon']!=""){
     
        let R = 6371;// km
        let RinM = (R*0.621371);
        let Lat1 = (parseFloat(lat.toFixed(5)));
        let Lon1 = (parseFloat(lon.toFixed(5)));
        let Lat2 = (parseFloat(this.getLocation[i]['Lat']));
        let Lon2 = (parseFloat(this.getLocation[i]['Lon']));
        let dLat = this.toRad(Lat2-Lat1);
        let dLon = this.toRad(Lon2-Lon1);
        let RLat1 = this.toRad(Lat1);
        let RLat2 = this.toRad(Lat2);
        let a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(RLat1) * Math.cos(RLat2);
        let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        //let d = R * c;
        let e = RinM * c;
        if(e < 5){
          tempLocation.push(this.getLocation[i]);
         }
       }
    }
    this.getLocation = tempLocation;
    return this.getLocation;
}

// Converts numeric degrees to radians
toRad(Value)
{
  return Value * Math.PI / 180;
}

I currently have the locations checking for a distance of 5 miles.

Any help would be appreciated.

Upvotes: 0

Views: 999

Answers (2)

McMurphy
McMurphy

Reputation: 1312

I recommend using a WebWorker so that the main UI thread isn't blocked for extended periods and a given event inspired search/filter can be interrupted of supplemented with more recent data: -

importScripts("Tier3Toolbox.js");

var currVintage = 0;
var inBounds = false;
var facFilter = [];
var imageProlog = "<div style='height:5em; width:5em; display:inline-block;vertical-align:middle;'>" +
                  "<img style='height:100%; width: 100%; max-height:100%; max-width:100%' src='";
var imageEpilog = "' ></div>";
var facilityTable, lineBreak;

self.addEventListener('message', function(e) 
{
  var data = e.data;
  switch (data.cmd) {
    case 'init':
      initThread(data.load);
      break;
    case 'initFilter':
      for (var i=0; i<data.filterTable.length; i++) {
        facFilter[data.filterTable[i].locTypeId] = {'icon':data.filterTable[i].icon};
      }
      break;
    case 'filter':
      facFilter = [];
      for (var i=0; i<data.filterTable.length; i++) {
        if (data.filterTable[i].facSelected)
          facFilter[data.filterTable[i].locTypeId] = {'icon':data.filterTable[i].icon};
      }
      break;
    case 'search':
      var searchVintage = ++currVintage;
      var tableSearch = new searcher(searchVintage, data);
      break;
    case 'reset':
      reset();
      self.postMessage({'reset': true});
      break;
    case 'stop':
      self.postMessage({'success' : true});
      self.close(); 
      break;
    default:
      self.postMessage({'success' : false, 'msg' : data.msg});
  };
}, false);

function initThread(msg) 
{
    facilityTable = JSON.parse(msg);
    reset();

    self.postMessage({'success' : true, 
                      'cnt'     : facilityTable.length
                    });     
}   

function reset() 
{
    for (var i=0; i<facilityTable.length; i++) {
        facilityTable[i].visible=false
    }
    currVintage = 0;
}   

function searcher(searchVintage, msg)
{
    var myVintage = searchVintage;
    var facIndex  = -1;
    var msg       = msg;

    var checkLoop = function()
    {
        if (myVintage != currVintage)
            return;

        if (++facIndex == facilityTable.length)
            return;

        inBounds = geoFencer.call(this, msg);

        if (inBounds) {
            var facMatch = 0;
            var bubbleHTML = "";
            for (var i=0; i<facilityTable[facIndex].facilities.length; i++){
                var currFac = facilityTable[facIndex].facilities[i];
                if (facFilter[currFac.locTypeId] != undefined) {
                    if (facMatch != 0) {
                        lineBreak = (facMatch / 3);
                        if (lineBreak == lineBreak.toFixed(0)) {
                            bubbleHTML += "<br />";
                        }
                    }
                    facMatch++;
                    bubbleHTML += imageProlog + facFilter[currFac.locTypeId].icon + imageEpilog;

                }
            }
            if (facMatch == 0) {
                inBounds = false;
            }
        }

        if (inBounds != facilityTable[facIndex].visible) {
            self.postMessage({'match'       : inBounds,
                              'facIndex'    : facIndex,
                              'scopeVintage': msg.scopeVintage,
                              'bubbleHTML'  : bubbleHTML,
                              'success'     : true
                            }); 
            facilityTable[facIndex].visible = inBounds;
        }

        setTimeout(checkLoop,0);
    }

    var circleCheck = function(msg) 
    {
        var diff = Tier3Toolbox.calculateDistance(
                        msg.centerLat,
                        msg.centerLng,
                        facilityTable[facIndex].searchLat,
                        facilityTable[facIndex].searchLng);

        if (msg.radius > diff)
            return true;        

        return false;
    }

    var rectangleCheck = function(msg) 
    {
        if (facilityTable[facIndex].searchLat > msg.SWLat &&
            facilityTable[facIndex].searchLat < msg.NELat &&
            facilityTable[facIndex].searchLng > msg.SWLng &&
            facilityTable[facIndex].searchLng < msg.NELng)
            return true;        

        return false;
    }

    var GEOFENCER = [circleCheck,rectangleCheck];
    var geoFencer = GEOFENCER[msg.checker];

    setTimeout(checkLoop,0);
    return this;
}

Upvotes: 0

Alnitak
Alnitak

Reputation: 339816

  1. don't store your lat/long values as strings - convert then to floating point at the earliest possible opportunity

  2. separate out your Haversine calculation into its own function

  3. consider using the Cosine variant of Haversine, and omitting the final acos step

  4. refactor constant expressions outside of the loop

  5. consider pre-calculating the radian equivalents for latitude and longitude

See also Quicker way to calculate geographic distance between two points

Upvotes: 1

Related Questions