GeoDjango – Save Two Point Fields for Geography and Geometry

distancegeodjangopointpostgis

Most of the explanations I've read for geography and geometry seem to suggest that one must only use either geometry or geography, and that the only way to use both is to cast/convert between one and the other on-the-fly. But is this actually the case?

Why not save two points?

class Point(models.Model):
    googlemaps_placeid = models.CharField(max_length=254)
    geography = models.PointField(geography=True)
    geometry= models.PointField(srid=3348) #Statistics Canada Lambert projection

Most of the queries from my users will be of the type "Show me all points within roughly 20km from this point, and order them from nearest to furthest". Even though the data has points all over Canada, the user is only interested in points within a small locality with any given query, so the geometry PointField would suffice.

If distance measurements in meters are required (as opposed to simply ordering them from nearest to furthest), then the geography PointField will be used.

Are there any flaws with this plan? If not, what is the actual syntax for saving the two PointFields? (I've searched all over for this since GeoDjango docs are severely lacking, and all I've found is this with no accepted answer: https://stackoverflow.com/questions/24921545/how-to-assign-to-a-django-pointfield-model-attribute)

Best Answer

I think there are some cases where you might justifiably store a point in both geography and geometry type but don't do it until you have to. I would recommend keeping things simple and choosing one or the other unless you run into actual performance issues that only this approach could solve.

The drawbacks are

  1. It's icky, like storing serialised data in a database. I'm not sure which normal form it violates but probably one them.
  2. You will have an extra field to write every time you save a new point or update and old one.

If you wanted to do it you would probably want to use a custom save method in your point model like this:

class Point(models.Model):
    googlemaps_placeid = models.CharField(max_length=254)
    geography = models.PointField(geography=True)
    geometry= models.PointField(srid=3348) #Statistics Canada Lambert projection
    objects = models.GeoManager()  # No need for this in 1.9+

    def save(self, *args, **kwargs):
        self.geography = self.geometry.transform(4326, clone=True)
        # ...any other custom save logic goes in here 
        super(Point, self).save(*args, **kwargs)

Obviously this would only work if you only ever saved or updated the geometry field and not the geography field elsewhere in your code.

Related Question