1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 | # Copyright (c) 2007, Justin Bronn
# All rights reserved.
#
# Released under New BSD License
#
"""
This module contains example models for the MaxMind(R) free
geolocation datasets, specifically, GeoLite Country and City:
http://www.maxmind.com/app/geolitecity
http://www.maxmind.com/app/geoip_country
To search for IP addresses requires long integers, however, Django
does not yet support these fields. These models require the BigIntegerField
patch by Peter Nixon:
http://code.djangoproject.com/attachment/ticket/399/django-bigint-20070712.patch
"""
from django.contrib.gis.db import models
import re
ipregex = re.compile(r'^(?P<w>\d\d?\d?)\.(?P<x>\d\d?\d?)\.(?P<y>\d\d?\d?)\.(?P<z>\d\d?\d?)$')
def ip2long(ip):
"Converts an IP address into a long suitable for querying."
m = ipregex.match(ip)
if m:
w, x, y, z = map(int, [m.group(g) for g in 'wxyz'])
ipnum = 16777216*w + 65536*x + 256*y + z
return ipnum
else:
raise TypeError, 'Invalid IP Address: %s' % ip
class IPManager(models.GeoManager):
def ipquery(self, ip):
"Returns any LocationBlocks matching the IP query."
num = ip2long(ip)
# Using select_related slows down this query a lot
qs = self.get_query_set().filter(ipfrom__lte=num).filter(ipto__gte=num)
if len(qs) == 1: return qs[0]
else: return qs
class Country(models.Model, models.GeoMixin):
name = models.CharField(maxlength=50)
code = models.CharField(maxlength=2)
point = models.PointField(null=True)
objects = models.GeoManager()
def __unicode__(self): return self.name
class CountryBlock(models.Model):
ipfrom = models.BigIntegerField(db_index=True)
ipto = models.BigIntegerField(db_index=True)
startip = models.IPAddressField()
endip = models.IPAddressField()
country = models.ForeignKey(Country)
objects = IPManager()
def __unicode__(self): return unicode('%s: %s to %s' % (self.country.name, self.startip, self.endip))
class Location(models.Model, models.GeoMixin):
locid = models.IntegerField(db_index=True)
country = models.ForeignKey(Country)
region = models.CharField(maxlength=7, blank=True)
city = models.CharField(maxlength=41, blank=True)
postalcode = models.CharField(maxlength=7, blank=True)
dmacode = models.CharField(maxlength=3, blank=True)
areacode = models.CharField(maxlength=3, blank=True)
point = models.PointField()
objects = models.GeoManager()
def __unicode__(self):
return unicode('Location %d: %s - %s, %s %s' % (self.locid, self.country.name, self.region, self.city, self.postalcode))
class LocationBlock(models.Model):
location = models.ForeignKey(Location)
ipfrom = models.BigIntegerField(db_index=True)
ipto = models.BigIntegerField(db_index=True)
objects = IPManager()
|
Comments
The ip2long(ip) can be done more effeciently via the Python struct & socket modules. They use the base C nhtoh() family. I also provided the inverse function.
I tested these on i686 hardware to be sure the unsigned integers matched the MaxMind CSV data sets. Other hardware may need the endian flag '>' switched around (See struct module docs).
import socket, struct
def ip4_to_int(ip): "Converts an IPv4 address, e.g. '208.0.1.4', into an unsigned long integer." return struct.unpack('>L',socket.inet_aton(ip))[0]
def int_to_ip4(num): "Converts an unsigned long integer into an IPv4 address." return socket.inet_ntoa(struct.pack('>L',num))
#
IPManager does not use GIS to Find Country or Location
The IPManager class does not take advantage of GeoDjango. It could create a 2D map of IP addresses, and query it with a spatial index.
There is a Blog on making a 2D map of IP addresses, with MySQL. It does not use Django to query the DB.
http://jcole.us/blog/archives/2007/11/24/on-efficiently-geo-referencing-ips-with-maxmind-geoip-and-mysql-gis/#comment-1290363
I am working on modifying the snippet module to use a 2D POLYGON map on the CountryBlocks & LocationBlocks models. It is a rectangle of IP (latitudes) & (-1, 0, 1) longitudes. The new field is:
ip_range = models.PolygonField()
When it's ready I'll post it with reference to this.
#
MySQL Users Limitations
GeoDjango's MySQL support has odd limitations. Here are some that I've found: 1) Only MyISAM Tables support a SPATIAL INDEX 2) GIS Fields, e.g. PointField(), must be non-NULL. Use: my_point = PointField(null=False) 3) Every GIS field gets a SPATIAL INDEX by default!
#
adroffner,
This was more of a "proof of concept" rather than for doing anything production. In fact, it's much faster to use GeoDjango's GeoIP support for the MaxMind binary databases.
"GeoDjango's MySQL support" has nothing to do with the limitations you gave -- those are limitations inherent to MySQL.
Finally, in the typical use case, spatial indexes are desirable for every GIS field. If this behavior annoys you, set
spatial_index=Falsein your field definition.#