-1

I am building a program that interacts with the Google Places API to identify all establishments of a given type within a U.S. County. Google accepts searches in the form of radiuses - so in order to cover an entire area, I am building my search radiuses out from one another sequentially. However, this algorithm creates a lot of overlapping circles that I would like to filter out. So:

Given a list of circles, with each's center and radius, how can I tell if a single circle is completely covered by any combination of other circles?

I can already tell if a circle is enveloped by another single circle - my problem is that a lot of them are enveloped by a combination of several others.

Someone asked for my existing code - the code I currently have tests if a circle is completely overlapped by another circle - not a combination of them. But here is what I have. You can see that I'm approximating the current problem by ruling it out if it overlaps with 20 other circles, at which point it's probably encompassed:

def radiusIsInsidePreviousQuery(self, testQuery):
    newSearchCoordinates = (testQuery['center']['lat'], testQuery['center']['lng'])
    alreadyBeenSearched = False

    numberOfIntersectingCircles = 0

    for queryNumber in self.results.keys():
        previousQuery = self.results[queryNumber]
        previousSearchCoordinates = (previousQuery['center']['lat'], 
                                     previousQuery['center']['lng'])

        centroidDistance = VincentyDistance(newSearchCoordinates, 
                                            previousSearchCoordinates)

        centroidDistanceMeters = centroidDistance.meters
        newQueryRadius = testQuery['radius']
        fullSearchDistance = centroidDistanceMeters + newQueryRadius

        #If the full search distance (the sum of the distance between
        #the two searches' centroids and the new search's radius) is less
        #than the previous search's radius, then the new search is encompassed
        #entirely by the old search.
        previousQueryRadius = previousQuery['radius']
        if fullSearchDistance <= previousQueryRadius:
            print "Search area encompassed"
            alreadyBeenSearched = True
        elif centroidDistanceMeters < newQueryRadius + previousQueryRadius:
            numberOfIntersectingCircles += 1
        elif self.queriesAreEqual(testQuery, previousQuery):
            print "found duplicate"
            alreadyBeenSearched = True   

    #If it intersects with 20 other circles, it's not doing any more good.
    if numberOfIntersectingCircles > 20:
        alreadyBeenSearched = True    

    return alreadyBeenSearched 
dataSci
  • 75
  • 1
  • 7

2 Answers2

0

You can address this as a disk union problem. This problem is related to the theory of Alpha shapes and can be solved via the construction of a weighted (additive) Voronoi diagram, that can be performed in time O(n Log(n)) for n disks.

You can use this construction as follows: compute the union of the disks from the list. Then add the single disk to this union. If there is no change, the single disk is encompassed.

You will have to use an advanced package such as CGAL, as the algorithm is far from simple.


If you can do with an approximate solution and are aiming at ease of programming, just paint the disks in a blank image at a suitable resolution. Then check if painting the single disk reaches new pixels.

This approach can be costly as you need to process a number of pixels equal to the total area of the disks.


A mixed solution is also possible as a compromise between difficulty and efficiency.

Choose a vertical resolution and cross-hatch the plane with equidistant horizontals. They will intersect every disk along a line segment. It is an easy matter to keep a list of segment for every horizontal, and perform unions of the segments as you add new disks. (The union of n segments is easily performed by sorting and counting the overlaps.)

  • Thank you for the idea! If I were going for absolute accuracy, I would definitely go for your first idea - however, I'm willing to accept some approximation so I like your latter, but I'm afraid I'm missing a bit of the terminology. Would you mind elaborating a bit on the significance of vertical resolutions and cross hatches? – dataSci Oct 09 '15 at 13:46
  • Parallel lines. This is a scanline approach. –  Oct 09 '15 at 13:47
  • Oh, I think I understand. So, cover the plane in cross-hatched parallel lines, and keep a list of the resulting cubes that are already covered by circles. If the new circle only covers previous ones, it's redundant? – dataSci Oct 09 '15 at 13:52
  • Well, if you create two planes of parallel lines - one vertical and one horizontal - you'll create a matrix that consists of cubes, right? – dataSci Oct 09 '15 at 14:02
  • In the third approach, there are no squares, just line segments. Only one scanning direction, this is what makes it an attractive solution (faster computation). –  Oct 09 '15 at 15:34
-1

Lets consider the circle to be tested A.

I don't know how precise your calculation needs to be but lets assume that representing circumference of A with 100 points is enough.

import math

#Circle to be tested

Circle_A = (3,2,1)
resolution = 100

circumference_A = []
for t in xrange(resolution):
    step = (2*math.pi)/resolution 
    x = Circle_A[0]
    y = Circle_A[1]
    r = Circle_A[2]
    circumference_A.append((x+(r*math.cos(t*step)),y+(r*math.sin(t*step))))

# Given list of circles (we need to know center and radius of each).
# Put them in a list of tuples like this (x,y,r)

ListOfCircles=[(5,2,2),(2,4,2),(2,2,1),(3,1,1)]

# Test:Check if all the points of circumference A are not < r from each one of the circles in the list.

overlap_count = 0
for p in circumference_A:
    print('testing p:',p)
    for c in ListOfCircles:
        distance = math.sqrt(math.pow(p[0]-c[0],2) + math.pow(p[1]-c[1],2))
        if distance < c[2]:
            overlap_count += 1
            print('overlap found with circle',c)
            break
        else:
            print('distance:',str(distance))
            print('origin:',c)



if overlap_count == resolution:
    print("Circle A is completely overlapped by the ListOfCircles")
else:
    print(str(overlap_count)+" points out of "+ str(resolution) + " overlapped with the composed area")
Ricardo Cid
  • 199
  • 1
  • 5
  • I would really love to know why this answer has been downvoted. The answer explains exactly what the OP needs to know. TL;DR : Test if any point of the circumference is within the area of each one of the given list of circles. – Ricardo Cid Oct 08 '15 at 19:50
  • Thanks so much for your help! So, maybe I'm misreading your code, and sorry if I am. But say that the list of circles you were testing happened to all be the three instances of the same circle - if the sum of their overlap_count was equal to the resolution, wouldn't this say the circle was completely enveloped, even though it's just the same portion of the circle being covered multiple times, and parts remain exposed? – dataSci Oct 09 '15 at 13:39
  • That is why the break statement is important. Once a point is inside it stops testing it so it won't give you false positives. You can just run the script as it is and start playing with it. The break statement also makes it very fast => resolution*log(n) being n the number of circles you are testing. Also it uses std library and no more. – Ricardo Cid Oct 10 '15 at 01:08