MaxMind(R) GeoIP Lite geolocation models

 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()

More like this

  1. MaxMind(R) GeoIP Lite CSV Import by jbronn 5 years, 10 months ago
  2. Redirect view based on GEO by jorjun 3 years, 9 months ago
  3. GoogleAdmin: GMaps base layer in Geographic Admin (GeoDjango) by jbronn 4 years, 7 months ago
  4. WorldIP - access to IP database over API by Alrond 4 years, 8 months ago
  5. Google v3 geocoding for Geodjango admin site by samhag 6 months, 2 weeks ago

Comments

adroffner (on February 6, 2008):

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))

#

adroffner (on February 6, 2008):

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.

#

adroffner (on February 7, 2008):

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!

#

jbronn (on March 11, 2008):

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=False in your field definition.

#

(Forgotten your password?)