Search code examples
azure-maps

Implementing Location-Based Marker Filtering in Microsoft Atlas Rest API


We are working on a project related to field clinicians and patients. We need to display a map (using Microsoft Atlas) on a page with multiple patient markers. However, we have a requirement that a field clinician can apply a setting to only view patients within a selected radius.

Does anyone know how we can implement this condition using the Atlas API? We want only the markers of patients within the radius selected by the field clinician to be displayed.

If anyone has a solution to this problem or any suggestions, please let us know. Thank you.

I have tried using the rest API to calculate distance between two coordinates but it's very expensive task to run for thousands of data.


Solution

  • When you say Microsoft Atlas, I'm assuming your mean the Azure Maps Web SDK which has a namespace of atlas.

    There are many ways to filter locations by distance (radius). The right approach would depend on the amount of data you plan to support within your application.

    Simple scenario

    In a simple application where the data is less than hundred thousand points, you can load all your data into your client application and do a distance calculation and filter of the data. Azure Maps Web SDK does have a built in math library that can be used, but you can also do this using the Haversine formula if you want the flexibility of doing this calculation in any part of your app without a dependency on Azure Maps. If you want to filter this data with the Azure Maps Web SDK there are two options:

    1. If you only need to visually filter and don't need the list of filtered locations outside of the map, you can use the distance data driven style expression with the filter option of a layer. In this scenario you would load all your points into a data source and connect it to a render layer (bubble, symbol...). Here is a code block sample that you would add inside the maps ready event handler.

      //Lets assume you have some position you want to use as the center of your search.
      var searchCenter = [-110, 45];
      
      //And a radius in meters.
      var searchRadius = 1000;
      
      //Create a data source.
      var datasource = new atlas.source.DataSource();
      
      //Add some data to the datasource
      datasource.add(...);
      
      //Add the datasource to the map.
      map.sources.add(datasource);
      
      //Create a layer to display the points.
      var layer = new atlas.layer.SymbolLayer(null, {
          //Add a filter option.
          filter: ['<=', ['distance', center], searchRadius]
      });
      
      //Add the layer to the map.
      map.layers.add(layer);
      

      Alternatively you can set/update the filter later by using the layers setOptions function. For example:

      layer.setOptions({ ['<=', ['distance', center], searchRadius] });
      

      Note, if you want the distance in a different unit, like miles, simply convert your distance to meters. You can either simply hard code this calculation, or use the atlas.math.convertDistance function: https://learn.microsoft.com/en-us/javascript/api/azure-maps-control/atlas.math?view=azure-maps-typescript-latest#azure-maps-control-atlas-math-convertdistance

    2. The second method is to loop through your data in code and calculate the distance from the center of your search to each point and filter it manually, then add the filtered points to the data source. This approach will be more efficient than the first one as the map would handle less data and your would be able to easily reuse the filtered data elsewhere in your app (e.g. show a list beside the map of filtered locations). You will also have the distance information which you can add as a property of the point as an added data point when you show details about that location. Here is a code block for this:

      //Lets assume you already have an empty data source and layer setup in your app.
      
      //Lets assume all your data is available as an array of GeoJSON point features. 
      var myData = [
          {
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [-110, 45]
            },
            "properties": {
              "myCustomProperty": 42
            }
          },
      
          //...more points.
      ];
      
      //Now lets create a reusable function to filter the data on the map.
      function filterData(center, radius) {
          //Use an array to capture which points match our filter.
          var filteredData = [];
      
          //Loop over all the points.
          for(var i = 0; i < myData.length; i++){
              //Calculate the distance from the center to each point.
              var d = atlas.math.getDistanceTo(center, myData[i]);
      
              //Check to see if it is within our radius.
              if(d <= radius) {
                  //Optionally capture the distance information so you can display it to the user later.
                  myData[i].properties.distance = d;
      
                  //Point matches filter, capture it.
                  filterData.push(myData[i]);
              }
          }
      
          //Overwrite all data in the datasource with the filtered data.
          datasource.setShapes(filterData);
      
          //Optionally. You can also use the filterData information to create a list outside the map if you want.
      }
      
      //You can then call this filter like this.
      filterData([-110, 45], 1000);
      

    If you would like to instead allow the user to draw an area on the map and select points, you can use the selection control: https://samples.azuremaps.com/?search=&sample=selection-control

    Also, if you have less than a few hundred thousand points, you can simply display all the points on the map and zoom the map into areas like this tutorial:

    There are other ways to filter locations as well that might be of interest, such as filtering by travel time or distance which may be better suited for a logistics type app. Here is a related code sample: https://samples.azuremaps.com/?sample=calculate-nearest-locations

    Advanced scenario

    If you have a lot of points, or want a better architected application, you can store your point data in a spatial database; SQL, PostgresSQL, CosmosDB, MySQL all support for spatial calculations on point data (some have very advanced spatial calculation support if needed). A big benefit of this approach is you can easily limit what data is sent down to the client application and greatly improving loading performance. For example, instead of loading all data when the app starts, you would only need to request the minium data needed to display the filtered locations on the map (the point position, a unique ID, and maybe name or any properties used for styling). Then, when the user selects a location, you can load detailed data for a location using its ID. In this scenario you would have the client application make a request to the backend servers for data (simple REST services, ASP.NET...). The backend code would then connect to the spatial database and do the spatial calculation.

    If using SQL, you would store your point data in a column with SqlGeography type. You can then do the same type of distance calculation and filter on the data. Here is some related documentation: https://learn.microsoft.com/en-us/sql/relational-databases/spatial/query-spatial-data-for-nearest-neighbor?view=sql-server-ver16

    With PostgresSQL you would want to load the PostGIS plugin to enable spatial support. https://www.postgis.net/workshops/postgis-intro/knn.html

    With CosmosDB you would use GeoJSON objects.

    If you do use a database, be sure to setup a spatial index as that can greatly improve performance.