Integrating with the app

As we continue to work with increasing levels of abstraction, think of our app's organization. We have two types of data and we have the GeocachingApp class with high level functionality. At this point, what we want is to enable the app to filter like we did in the tests, but in a simple and straightforward way.

Take a look at the app as it is at this point:

class GeocachingApp(PointCollection):
    def __init__(self, data_file=None, my_location=None):
        """Application class.

        :param data_file: An OGR compatible file
         with geocaching points.
        :param my_location: Coordinates of your location.
        """
        super(GeocachingApp, self).__init__(file_path=data_file)

        self._datasource = None
        self._transformed_geoms = None
        self._my_location = None
        self.distances = None

        if my_location:
            self.my_location = my_location

    @property
    def my_location(self):
        return self._my_location

    @my_location.setter
    def my_location(self, coordinates):
        self._my_location = transform_points([coordinates])[0]

    def calculate_distances(self):
        """Calculates the distance between a
        set of points and a given location.

        :return: A list of distances in the same order as
         the points.
        """
        xa = self.my_location[0]
        ya = self.my_location[1]
        points = self._transformed_geoms
        distances = []
        for geom in points:
            point_distance = math.sqrt(
                (geom.GetX() - xa)**2 + (geom.GetY() - ya))
            distances.append(point_distance)
        return distances

    def find_closest_point(self):
        """Find the closest point to a given location and
        return the cache that's on that point.

        :return: OGR feature containing the point.
        """
        # Part 1.
        distances = self.calculate_distances()
        index = np.argmin(distances)
        # Part 2.
        layer = self._datasource.GetLayerByIndex(0)
        feature = layer.GetFeature(index)
        print "Closest point at: {}m".format(distances[index])
        return feature

Inheritance was used to give the app the functionality contained in the PointCollection class. But this schema won't work anymore because we now have two types of data. We have to remove the inheritance and make a different approach.

What we will do is store instances of the collection classes (PointCollection and BoundaryCollection), and implement the methods that will relate them in the same way it was done in the tests of the chaining filters topic.

Let's start with the imports and the class' definition:

  1. Open your geocaching_app.py file and edit the imports section at the beginning of the file to include new classes:
    # coding=utf-8
    
    import gdal
    import numpy as np
    import math
    from utils.geo_functions import transform_points
    from models import PointCollection, BoundaryCollection
  2. Now, edit the GeocachingApp class definition and the __init__ method to be as follows:
    class GeocachingApp(object):
        def __init__(self,
                     geocaching_file=None,
                     boundary_file=None,
                     my_location=None):
            """Application class.
    
            :param geocaching_file: An OGR compatible file
             with geocaching points.
            :param boundary_file: A file with boundaries.
            :param my_location: Coordinates of your location.
            """
            self.geocaching_data = PointCollection(geocaching_file)
            self.boundaries = BoundaryCollection(boundary_file)
            self._my_location = None
            if my_location:
                self.my_location = my_location

    The inheritance was removed and now the data is stored in the geocaching_data and boundaries properties. Optionally, if the user passes a file with geocaching data or with boundary data to the GeocachingApp class, these same files are passed as an argument to the PointCollection and BoundaryCollection creations.

    With what you have now, you can already do any type of filtering. You just need to access geocaching_data and boundaries and do exactly what we did before. Let's try it.

  3. Go to the end of the file where there is a line with if __name__ == "__main__": and edit the code:
    if __name__ == "__main__":
        my_app = GeocachingApp("../data/geocaching.gpx",
                               "../data/world_borders_simple.shp")
        usa_boundary = my_app.boundaries.get_by_name('United States')
        result = my_app.geocaching_data.filter_by_boundary(
            usa_boundary)
        print(result)
  4. Now run it. Remember that whenever you want to run a different file, you need to press Alt + Shift + F10 and choose the file in the popup. You should see the output with the list of geocaching points again.

    But let's suppose that there is a kind of filtering that is expected to be needed multiple times or, maybe, there is a filtering that you want to make explicit. Following the same example, suppose that we are filtering by a country name in this case.

    We can use the GeocachingApp class, which stands in the highest level of abstraction in our code to implement this or any other high level filtering method.

  5. Add this method to the GeocachingApp class:
    #...    
        def filter_by_country(self, name):        
            """Filter by a country with a given name.
    
            :param name: The name of the boundary (ex. county name)
            :return: PointCollection
            """
            boundary = self.boundaries.get_by_name(name)        
            return self.geocaching_data.filter_by_boundary(boundary)

In computer programming, this is also called convenience method. It's a method created for convenience in order to solve a more complex task or to avoid boilerplate code (that is, to avoid code repetition).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.35.185