Commits

spri...@gmail.com  committed 4f272a9

Adding basics of working files for shape handling

  • Participants
  • Parent commits a281d41

Comments (0)

Files changed (5)

 
 Working with Shapefiles in GeoDjango.
 
-Stub.
+To enable this application:
+
+ - Add `shapes` to your INSTALLED APPS
+ - Add `(r'^shapes/', include('shapes.urls')),` to your urlpatterns
+ 
+This application exposed two urls:
+
+ /shapes/upload/
+
+ /shapes/export/
+ 
+
+Working with mercurial
+----------------------
+
+$ hg pull
+
+$ hg update
+
+$ hg ci -m "message" <file>
+
+$ hg push
+
+
+More info
+---------
+
+http://www.bitbucket.org/springmeyer/django-shapes/

File shapes/forms.py

+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django.conf import settings
+from django.contrib.gis.gdal import DataSource
+from django.forms.util import ValidationError
+import zipfile
+import tempfile
+
+#http://docs.djangoproject.com/en/dev/topics/http/file-uploads/
+#http://www.neverfriday.com/sweetfriday/2008/09/-a-long-time-ago.html 
+
+class UploadForm(forms.Form):
+
+    file_obj  = forms.FileField(label=_('Upload a Zipped Shapefile'))
+    # TODO:
+    # collect attribute info to stick in potential model
+    #title = forms.CharField(max_length=50,label=_('Title'))
+    #epsg = forms.IntegerField()
+    
+    def clean_file_obj(self):
+      f = self.cleaned_data['file_obj']
+      valid_shp, error = self.validate(f)
+      if not valid_shp:
+        raise ValidationError("A problem occured: %s" % error)
+
+    def handle(self,uploaded_file):
+      downloaded_file = '%s/%s' % (settings.SHP_UPLOAD_DIR, uploaded_file)
+      destination = open(downloaded_file, 'wb+')
+      for chunk in uploaded_file.chunks():
+          destination.write(chunk)
+      destination.close()        
+
+    def validate(self,uploaded_file):
+      tmp = tempfile.NamedTemporaryFile(suffix='.shp', mode = 'w')
+      tmp_dir = tempfile.gettempdir()
+      destination = open(tmp.name, 'wb+')
+      for chunk in uploaded_file.chunks():
+          destination.write(chunk)
+      destination.close()
+      if not zipfile.is_zipfile(tmp.name):
+        return False, 'That file is not a Zip Archive'
+      else:
+        zfile = zipfile.ZipFile(tmp.name)
+        if not True in [info.filename.endswith('shp') for info in zfile.infolist()]:
+          return False, 'Found Zip Archive but no file with a .shp extension found inside'
+        else:
+          if not True in [info.filename.endswith('prj') for info in zfile.infolist()]:
+            return False, 'You must supply a .prj file with the Shapefile to indicate the projection'
+          else:
+            for info in zfile.infolist():
+              data = zfile.read(info.filename)
+              shp_part = '%s%s' % (tmp_dir, info.filename)
+              fout = open(shp_part, "wb")
+              fout.write(data)
+              fout.close()
+              # http://code.djangoproject.com/wiki/GeoDjangoExtras#DataSource
+            ds_name = zfile.infolist()[0].filename.split('.')[0]
+            ds = DataSource('%s%s.shp' % (tmp_dir, ds_name))
+            layer = ds[0]
+            if layer.test_capability('RandomRead'):
+              if ds._driver.__str__() == 'ESRI Shapefile':
+                return True, None
+              else:
+                return False, "Sorry, we've experienced a problem on our server. Please try again later."
+            else:
+                return False, 'Cannot read the shapefile, data is corrupted inside the zip, please try to upload again' 

File shapes/views/export.py

+import os
+import shutil
+import zipfile
+import tempfile
+import datetime
+
+from django.db.models.query import QuerySet, ValuesQuerySet
+from django.contrib.gis.db.models.fields import GeometryField
+from django.utils import simplejson
+from django.http import HttpResponse
+from django.utils.encoding import smart_str, smart_unicode
+from django.utils.translation import ugettext as _
+
+# ctypes stuff
+from cStringIO import StringIO
+from ctypes import string_at, c_double
+from django.contrib.gis.gdal.libgdal import lgdal
+
+#from django.contrib.gis.gdal import *
+from django.contrib.gis.gdal import OGRGeomType, SpatialReference, check_err
+
+# todo use: qs.query._geo_field()
+# todo abstract lgdal stuff
+# todo: support multiple querysets == multiple shapefiles
+
+class ShpResponder(object):
+    def __init__(self, queryset,*args, **kwargs):
+        # Available data
+        self.queryset = queryset
+    
+    def __call__(self, request, *args, **kwargs):
+        
+        return export(self.queryset, **kwargs)
+
+def export(query_set, geom_field=None, proj_transform=4326,mimetype='application/zip'):
+    
+    lgdal.OGRRegisterAll()
+    
+    tmp = tempfile.NamedTemporaryFile(suffix='.shp', mode = 'w')
+    path = os.path.dirname(tmp.name)
+    name = tmp.name.split('/')[-1]
+    fullname = tmp.name
+    basename = fullname.strip('.shp')
+    tmp.close()
+    
+    # Todo: contruct download name dynamically from queryset
+    download_name = 'shp_download'
+    
+    fields = query_set.model._meta.fields
+    geo_fields = [f for f in fields if isinstance(f, GeometryField)]
+    other_fields = [f for f in fields if not isinstance(f, GeometryField)]
+    
+    if len(geo_fields) > 1:
+        geo_field = geo_fields[0] # no support yet for multiple geometry fields
+    else:
+        geo_field = geo_fields[0]
+    
+    srs = SpatialReference(geo_field._srid)
+    dr = lgdal.OGRGetDriverByName('ESRI Shapefile')
+
+    # Creating the datasource
+    ds = lgdal.OGR_Dr_CreateDataSource(dr,fullname, None)
+    
+    # Get the right geometry type number for ogr
+    ogr_type = OGRGeomType(geo_field._geom).num
+    
+    # Creating the layer
+    layer = lgdal.OGR_DS_CreateLayer(ds,name, srs._ptr, ogr_type, None)
+    #import pdb;pdb.set_trace()
+    
+    for field in other_fields:
+      fld = lgdal.OGR_Fld_Create(str(field.name), 4)
+      added = lgdal.OGR_L_CreateField(layer, fld, 0)
+      check_err(added) 
+    
+    # Getting the Layer feature definition.
+    fdefn = lgdal.OGR_L_GetLayerDefn(layer) 
+    
+    for item in query_set:
+    
+        feat = lgdal.OGR_F_Create(fdefn)
+        
+        # Setting the fields (for now all as strings)
+        # TODO: catch model types and convert to ogr fields
+        #OFTReal => FloatField DecimalField
+        #OFTInteger => IntegerField
+        #OFTString => CharField
+        #OFTDate => DateField
+        #OFTDateTime => DateTimeField
+        #OFTDate => TimeField
+        
+        idx = 0
+        for field in other_fields:
+          value = getattr(item,field.name)
+          lgdal.OGR_F_SetFieldString(feat, idx, str(value))
+          idx += 1
+          
+        # Transforming & setting the geometry
+        geom = getattr(item,geo_field.name)
+        ogr_geom = OGRGeometry(geom.wkt,srs)
+        ogr_geom.transform(proj_transform)
+        check_err(lgdal.OGR_F_SetGeometry(feat, ogr_geom._ptr))
+        # Creating the feature in the layer.
+        check_err(lgdal.OGR_L_SetFeature(layer, feat))
+    
+    # Cleaning up
+    check_err(lgdal.OGR_L_SyncToDisk(layer))
+    lgdal.OGR_DS_Destroy(ds)
+
+    buffer = StringIO()
+    zip = zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED)
+    files = ['shp','shx','prj','dbf']
+    for item in files:
+      filename= '%s.%s' % (basename, item)
+      if os.path.exists(filename):
+        print 'yes:%s' % filename
+      else:
+        print 'no:%s' % filename
+      zip.write(filename, '%s.%s' % (download_name, item))
+    zip.close()
+    buffer.flush()
+    ret_zip = buffer.getvalue()
+    buffer.close()     
+    response = HttpResponse()
+    response['Content-Disposition'] = 'filename=%s.zip' % download_name
+    response['Content-length'] = str(len(ret_zip))
+    response['Content-Type'] = mimetype
+
+    response.write(ret_zip)
+    lgdal.OGRCleanupAll()
+    return response

File shapes/views/upload.py

+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from shapes.forms import UploadForm
+
+
+# TODO convert this to using ModelForm with a custom Django FileField
+# For now we just stick an uploaded shapefile into a project directory
+
+def upload(request):
+    if request.method == 'POST':
+        form = UploadForm(request.POST, request.FILES)
+        if form.is_valid():
+            form.handle(request.FILES['file_obj'])
+            #form.save() # if a modelform
+            #form.cleaned_data['user'] = request.user
+            return render_to_response('uploaded.html', RequestContext(request,{}))
+    else:
+        form = UploadForm()
+    return render_to_response('upload.html', RequestContext(request,{'form': form}))
+from django.conf import settings
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from django.contrib import databrowse
+
+from world.views import welcome
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    (r'^$', welcome),
+    (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+    
+    (r'^shapes/', include('shapes.urls')),
+    (r'^admin/(.*)', admin.site.root),
+    (r'^databrowse/(.*)', databrowse.site.root),
+)
+    
+
+if settings.DEBUG:
+    urlpatterns += patterns('',
+        (r'^media/(.*)$','django.views.static.serve',{'document_root': settings.MEDIA_ROOT, 'show_indexes': True})
+    )