Exploring the Geospatial Overlap of Two Polygons in MongoDB

Is it possible to use a mongodb geospatial query to retrieve location data that meets the following criteria?

  • Retrieving all locations that fall within the intersection of two boxes or any two polygons in general.

For example, is it feasible to only output those locations that are within the yellow area, which represents the shared region between the purple and red geometric objects [polygons]?

My research on mongodb documentation so far:

Use case:

    db.places.find( {
   loc: { $geoWithin: { $box:  [ [ 0, 0 ], [ 100, 100 ] ] } }
} )

The above query returns results within a rectangular geometric area [I am interested in locations common to two such individual queries].

    db.places.find( {
   loc: { $geoWithin: { $box:  [ [ 0, 0 ], [ 100, 100 ] ] } }
} )

    db.places.find( {
   loc: { $geoWithin: { $box:  [ [ 50, 50 ], [ 90, 120 ] ] } }
} )

Answer №1

Upon revisiting this with a fresh perspective, the solution becomes glaringly obvious. The main point you have already mentioned is your desire to discover the "intersection" of two queries within a single response.

Another angle to approach this is by considering that you want all the points constrained by the first query to act as the "input" for the second query, and so forth as needed. Essentially, this is what an intersection accomplishes, although the logic is taken quite literally.

To achieve this, utilize MongoDB's aggregation framework to sequentially connect the matching queries. For example, let's consider the following sample documents:

{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }

Now, here is the chained aggregation pipeline comprising just two queries:

db.geotest.aggregate([
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [0,0], [10,10] ]
            }
        }
    }},
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [5,5], [20,20] ]
            }
        }
    }}
])

In logical terms, the first query identifies the points falling within the boundaries of the initial box or the first two items. These results are then fed into the second query. As the new box limits start at [5,5], the first point gets excluded. Additionally, the third point was already eliminated, but if the box restrictions were reversed, only the middle document would remain in the result.

The functioning of this operation is unique to the $geoWithin query operator compared to other geo functions:

$geoWithin does not require a geospatial index. However, a geospatial index will enhance query performance. Both 2dsphere and 2d geospatial indexes support $geoWithin.

The outcomes have their pros and cons. While it's advantageous to perform this type of operation without an existing index, once the aggregation pipeline alters the collection results post the primary query operation, no further index can be leveraged. Thus, any indexing benefits are nullified upon merging the "set" results from operations beyond the initial Polygon/MultiPolygon, as supported.


Hence, I suggest computing the intersection bounds "externally" to the MongoDB query. Despite the aggregation framework's ability to accomplish this through its "chained" pipeline nature and diminishing intersection sizes, optimal performance can be achieved with a single query encompassing the correct bounds that effectively utilizes all the index advantages.

There exist various methods to achieve this, but for reference, here is an implementation utilizing the JSTS library, which serves as a JavaScript rendition of the popular JTS library for Java. There might be other libraries or language translations available, but this one offers straightforward GeoJSON parsing capabilities and pre-built methods for actions like deriving intersection bounds:

var async = require('async');
    util = require('util'),
    jsts = require('jsts'),
    mongo = require('mongodb'),
    MongoClient = mongo.MongoClient;

var parser = new jsts.io.GeoJSONParser();

var polys= [
  {
    type: 'Polygon',
    coordinates: [[
      [ 0, 0 ], [ 0, 10 ], [ 10, 10 ], [ 10, 0 ], [ 0, 0 ]
    ]]
  },
  {
    type: 'Polygon',
    coordinates: [[
      [ 5, 5 ], [ 5, 20 ], [ 20, 20 ], [ 20, 5 ], [ 5, 5 ]
    ]]
  }
];

var points = [
  { type: 'Point', coordinates: [ 4, 4 ]  },
  { type: 'Point', coordinates: [ 8, 8 ]  },
  { type: 'Point', coordinates: [ 12, 12 ] }
];

MongoClient.connect('mongodb://localhost/test',function(err,db) {

  db.collection('geotest',function(err,geo) {

    if (err) throw err;

    async.series(
      [
        // Insert some data
        function(callback) {
          var bulk = geo.initializeOrderedBulkOp();
          bulk.find({}).remove();
          async.each(points,function(point,callback) {
            bulk.insert({ "loc": point });
            callback();
          },function(err) {
            bulk.execute(callback);
          });
        },

        // Execute each query version
        function(callback) {
          async.parallel(
            [
              // Aggregation
              function(callback) {
                var pipeline = [];
                polys.forEach(function(poly) {
                  pipeline.push({
                    "$match": {
                      "loc": {
                        "$geoWithin": {
                          "$geometry": poly
                        }
                      }
                    }
                  });
                });

                geo.aggregate(pipeline,callback);
              },

              // Employ external set resolution
              function(callback) {
                var geos = polys.map(function(poly) {
                  return parser.read( poly );
                });

                var bounds = geos[0];

                for ( var x=1; x<geos.length; x++ ) {
                  bounds = bounds.intersection( geos[x] );
                }

                var coords = parser.write( bounds );

                geo.find({
                  "loc": {
                    "$geoWithin": {
                      "$geometry": coords
                    }
                  }
                }).toArray(callback);
              }
            ],
            callback
          );
        }
      ],
      function(err,results) {
        if (err) throw err;
        console.log(
          util.inspect( results.slice(-1), false, 12, true ) );
        db.close();
      }
    );

  });

});

Incorporate full GeoJSON "Polygon" representations as such translates to what JTS comprehends and works with. It's likely that any input for a real application would follow this format rather than opting for conveniences like $box.

While achieving this task via the aggregation framework or parallel queries combining result sets is feasible, albeit the aggregation framework might handle it better than external result set merging, superior outcomes stem from initially calculating the bounds.

Answer №2

If you happen to stumble across this information, it's worth noting that starting from mongo version 2.4, the $geoIntersects operator can be used to locate the intersection of GeoJSON objects. This feature allows for finding intersections between two polygons, among other geometries.

{
  <location field>: {
     $geoIntersects: {
        $geometry: {
           type: "<GeoJSON object type>" ,
           coordinates: [ <coordinates> ]
        }
     }
  }
}

You may also find a detailed explanation on this blog.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Radio button fails to be marked as selected

Currently, I am conducting a test on , and one of the tasks involves styling Radio Buttons using JavaScript (jQuery). Despite my repeated attempts to reconstruct the code, I always encounter the same issue: although JS successfully sets the input radio but ...

Problem with the show/hide feature on jQuery. Automatically scrolls to the beginning of the page

On my website, I have successfully implemented two basic Show / Hide links that are working great. Here is the HTML code: <!DOCTYPE html> <html lang="en"> <head profile="http://gmpg.org/xfn/11"> <meta http-equiv="Content-Type" conte ...

The versatility of Protractor's variable scope when working with promises

Insight into Angular App Testing As I delved into testing my Angular app using ng-repeat to create a table, a user brought to my attention the presence of duplicate entries. Eager to address this issue, I visually confirmed it and proceeded to craft a Pro ...

Retrieving JSON data from a form in a Node.js application

Situation : Here is the HTML code I am working with <form action="http://example.com/nodejs/endpoint" method="post" enctype="multipart/form-data"> <label> Select JSON file <input type="file" name="json"> ...

What is the best approach for MongoDB documents: keep fields with empty values or omit them entirely

For my current project, I am delving into MongoDB for the first time and I find myself pondering the best approach for handling blank or unset values in a document. When it comes to pairs that are expected to have values in the future, which method is more ...

Determine the precise boundaries of the React component

I am working with a basic ellipse element: <span style={{ width: /*someWith*/, height: /*someHeight*/, borderRadius: "50%" }}/> and, I am using getBoundingClientRect() to retrieve its bounds (displayed in blue). https://i.ssta ...

What is the best way to sift through an array containing arrays of arrays that hold objects?

My attempt to filter an array of arrays of objects is not working as expected. constructNewGrid(filterText){ if(searchText == ""){ this.constructedGrid = this.fullGrid; return; } this.constructedGrid = []; this.c ...

Ways to include several conditions in ng-class with Angular.js

I want to include multiple conditions within ng-class using Angular.js. Let me elaborate on my code below. <li ng-class="{'active':($root.stateName==='app.settings')}"><a ui-sref="app.settings">Settings</a></li> ...

Why is my API call using Mongoose's .find() method with a filter not returning any results?

Today, a strange occurrence unfolded. Out of the blue, an API call using Mongoose/Node that was previously functional suddenly stopped fetching results. I tried testing the call in Insomnia as well, only to find it returns no data and no errors either. Her ...

"Instead of seeing the expected content, a blank page is showing

I've been searching for a solution to this problem without any luck. Any assistance would be greatly appreciated. Initially, I created a "tabs" default project which worked fine as a base. However, after making a few modifications, the screen ended u ...

Exploring promises with loops and patience?

I created a function that accesses an API and retrieves an array containing 100 objects at once. The getPageData() function works as expected when passing an integer without the loop. However, when attempting to iterate through it, I am not getting any res ...

"Utilize an Ajax form with the 'post' method

I'm facing an issue with the AJAX form as it keeps throwing an error. I really need it to function properly when a button is clicked, refreshing all data in the process. <form name="form" id="form" class="form"> ...

I am experiencing an issue in my React application where the component is not being rendered when using a nested route with React Router Dom v6. Despite the

On my main page, I have a sidebar and a content display area that shows the selected option. The issue arises when I click on a link in the sidebar - it doesn't change anything in the content display section, only the URL changes. If I define the rout ...

The function recipes.map is not compatible with ReactJS

I am facing an issue while trying to display elements from an array as components in ReactJS. The error message 'recipes.map is not a function' keeps popping up when I execute my code. Below is the snippet of my code: fetchRecipes() { va ...

Effective implementation of the useReducer hook across various React components to manage form state

My current project is proving to be quite challenging as I navigate through the step-by-step instructions provided. Initially, I was tasked with creating a BookingForm.js component that houses a form for my app. The requirement was to utilize the "useEffec ...

Javascript - Single line conditional statement

As I continue to improve my JavaScript skills, I'm looking for guidance on optimizing the following if statement. Is there a way to shorten it, possibly even condense it into one line? How can I achieve that? onSelect: function (sortOption) { th ...

How to successfully integrate the three.js example of webgl_loader_obj_mtl.html into an ASP.NET WebForm, even when encountering issues with mtlLoader.setPath

While attempting to integrate the webgl_loader_obj_mtl.html example from three.js into an ASP.NET WebForm, I encountered an issue. Upon running the HTML, Visual Studio 2015 failed at mtlLoader.setPath. Has anyone else experienced the same problem? Addition ...

Tips for properly handling special characters in AJAX-loaded content?

During my ajax call, I encountered the following issue upon success: success: function(html){ var product_json = []; data=$(html); $(".product_json", data).each(function(){ product_json.push( jQuery.parseJSON( $(this).html() ) ); }); .... //code co ...

Automatically start playing HTML5 audio/video on iOS 5 devices

Currently, I am facing a challenge with implementing sound effects in my HTML5 web-app on iOS5. Despite trying various methods, the sound effects are not working as expected. Are there any creative solutions available to gain JavaScript control over an HT ...

Using a Do/While loop in combination with the setTimeout function to create an infinite loop

Having trouble with this script. The opacity transition works fine, but there seems to be an issue with the loop causing it to run indefinitely. My goal is to change the z-index once the opacity reaches 0. HTML <div class="ribbon_services_body" id="ri ...