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:
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
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.
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)
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.
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).
3.141.35.185