commit d645ccdd476aa2232af5da2125a1d60393acd195 Author: Elf M. Sternberg Date: Wed Oct 13 09:05:17 2010 -0700 Initial commit of working zipdistance. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d233b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.pyc +*.rej +*.orig +*.pyo +*# +.#* +.DS_Store +*~ +indieflix/stages/chi/ +indieflix/static/avatars/ +*.xcf +common/ctctwspylib/.cache diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..c94b8b7 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Elf M. Sternberg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a69722e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2008 Elf M. Sternberg and all contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE AND DATA IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..fea3e35 --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +============================== +django-zipdistance +============================== + +django-zipdistance is a simple application to find the distance +between any two zip codes. The fixture provided with this application +is derived from the Year 2000 Zip Code Tabulation Area (ZCTA) tables +provided by the US Census Bureau. The Census Bureau notes: + + ZCTAs are generalized area representations of U.S. Postal Service + (USPS) ZIP Code service areas. In most instances the ZCTA code + equals the ZIP Code for an area. Some ZIP Codes represent very + few addresses (sometimes only one) and therefore will not appear + in the ZCTA database. ZCTA is a trademark of the U.S. Census + Bureau; ZIP Code is a registered trademark of the U.S. Postal + Service. + +In short, the ZCTAs most likely, but are not guaranteed to, correspond +with ZIP Codes for any given address. In testing, this database +proved to be entirely acceptable for most of the United States. But +just to make sure, I repeat: THE SOFTWARE AND DATA FILES ARE PROVIDED +"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +----------------------------- +Standard Usage: + +Find all the zipcodes within 50 miles: + + target = Zipmap.objects.get(zipcode = zip_form.cleaned_data['zipcode1'].strip()) + zips = Zipmap.objects.distance_from(target, 50) + +Find the distance between two zipcodes: + + zip1 = Zipmap.objects.get(zipcode = zip_form.cleaned_data['zipcode1'].strip()) + zip2 = Zipmap.objects.get(zipcode = zip_form.cleaned_data['zipcode1'].strip()) + zip1.distance_between(zip_2) + +Find all the stores within a given distance. + + zips = Zipmap.objects.distance_from(target, 50) + stores = Store.objects.get(zipcode__in = [z.zipcode for z in zips]) + +That last one's not terribly efficient. If you used the ZipMap as a +way of storing zipcodes, you could probably come up with something +smarter. + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..53d9415 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Django==1.2 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..11e14f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages + +setup ( + name='django-zipdistance', + version='0.1', + description='A zip code distance application for Django.', + author='Elf M. Sternberg', + author_email='elf.sternberg@gmail.com', + url='http://github.com/elfsternberg/django-zipdistance/', + license='MIT License', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + package_data = { + '': ['*.yaml.gz'] + }, + packages=find_packages(), +) diff --git a/zipdistance/__init__.py b/zipdistance/__init__.py new file mode 100644 index 0000000..566731c --- /dev/null +++ b/zipdistance/__init__.py @@ -0,0 +1,32 @@ +# Copyright 2010 by Elf M. Sternberg. All rights not expressly granted +# herein are reserved. +# +# Created in the United States of America. +# +# This digital media is protected by U.S. and international copyright +# and intellectual property laws. Unless otherwise specified, all +# information and screens appearing as part of this digital medium, +# including software, services, documents, text, images, icons, and +# logos design; the selection, assembly, arrangement, and design +# thereof; and the code that enables its presentation, are the sole +# property of Elf M. Sternberg. + +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE AND DATA IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/zipdistance/fixtures/initial_data.yaml.gz b/zipdistance/fixtures/initial_data.yaml.gz new file mode 100644 index 0000000..41e5c1c Binary files /dev/null and b/zipdistance/fixtures/initial_data.yaml.gz differ diff --git a/zipdistance/models.py b/zipdistance/models.py new file mode 100644 index 0000000..df7bbe4 --- /dev/null +++ b/zipdistance/models.py @@ -0,0 +1,74 @@ +from django.db import models +from django.db.models import Aggregate +from django.db.models.sql.aggregates import Aggregate as AggregateImpl + +class DistanceFromImpl(AggregateImpl): + sql_function = '' + is_computed = True + is_ordinal = True + + sql_template = ('3959 * acos( cos( radians(%(t_lat)f) ) * cos( radians( latitude ) ) * ' + 'cos( radians( longitude ) - radians(%(t_lon)f) ) + sin( radians(%(t_lat)f) ) * ' + 'sin( radians( latitude ) ) )') + + def __init__(self, col, target, **extra): + self.col = col + self.target = target + self.extra = extra + + def _default_alias(self): + return '%s__%s' % (str(self.target), self.__class__.__name__.lower()) + + default_alias = property(_default_alias) + + def add_to_query(self, query, alias, col, source, is_summary): + super(DistanceFrom, self).__init__(col, source, is_summary, **self.extra) + query.aggregate_select[alias] = self + + def as_sql(self, qn, connection): + "Return the aggregate, rendered as SQL." + + return self.sql_template % { 't_lon': self.target.longitude, + 't_lat': self.target.latitude } + + + +class DistanceFrom(Aggregate): + name="DistanceFromImpl" + + def add_to_query(self, query, alias, col, source, is_summary): + aggregate = DistanceFromImpl(col, source=source, is_summary=is_summary, **self.extra) + query.aggregates[alias] = aggregate + + + +class ZipDistanceManager(models.Manager): + + def distance_from(self, target, limit = 0): + qs = self.annotate(distance = DistanceFrom('zipcode', target = target)) + if bool(limit): + qs = qs.filter(distance__lte = float(limit)) + qs = qs.order_by('distance') + return qs + + + +class ZipDistance(models.Model): + state = models.CharField(max_length = 2) + zipcode = models.CharField(max_length = 5, unique = True) + latitude = models.FloatField() + longitude = models.FloatField() + + objects = ZipDistanceManager() + + class Meta: + ordering = ['zipcode'] + + def __unicode__(self): + return '%s:%s' % (self.state, self.zipcode) + + def distance_between(self, other): + return self.__class__.objects.distance_from(self).get(zipcode = other.zipcode).distance + + + diff --git a/zipdistance/tests.py b/zipdistance/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/zipdistance/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/zipdistance/views.py b/zipdistance/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/zipdistance/views.py @@ -0,0 +1 @@ +# Create your views here.