Exporting Shapefiles

We next need to implement the ability to export a Shapefile. The process of exporting a Shapefile is basically the reverse of the import logic, and involves the following steps:

  1. Create an OGR Shapefile to receive the exported data.
  2. Save the features into the Shapefile.
  3. Save the attributes into the Shapefile.
  4. Compress the shapefile into a ZIP archive.
  5. Delete our temporary files.
  6. Send the ZIP file back to the user's web browser.

All this work will take place in the shapefileIO.py module, with help from some utils.py functions. Before we begin, let's define the exportData() function so that we have somewhere to place our code. Edit shapefileIO.py, and add the following new function:

def exportData(shapefile):
    return "More to come..."

While we're at it, let's create the "export shapefile" view function. This will call the exportData() function to do all the hard work. Edit views.py and add the following new function:

def exportShapefile(request, shapefile_id):
  try:
    shapefile = Shapefile.objects.get(id=shapefile_id)
  except Shapefile.DoesNotExist:
    raise Http404

  return shapefileIO.exportData(shapefile)

This is all pretty straightforward. Then, edit urls.py and add the following entry to the geodjango.shapeEditor.views URL pattern list:

  (r'^shape-editor/export/(?P<shapefile_id>d+)$',
   'exportShapefile'),

We've already got the /export URL defined in our "list shapefiles" view, so the user can click on the Export hyperlink to call our view function. This in turn will call shapefileIO.exportData() to do the actual exporting. Let's start implementing that exportData() function.

Define the OGR Shapefile

We'll use OGR to create the new Shapefile that will hold the exported features. Let's start by creating a temporary directory to hold the Shapefile's contents; replace your placeholder version of exportData() with the following:

def exportData(shapefile):
    dstDir = tempfile.mkdtemp()
    dstFile = str(os.path.join(dstDir, shapefile.filename))

Now that we've got somewhere to store the Shapefile (and a filename for it), we'll create a spatial reference for the Shapefile to use, and set up the Shapefile's datasource and layer:

    dstSpatialRef = osr.SpatialReference()
    dstSpatialRef.ImportFromWkt(shapefile.srs_wkt)

    driver = ogr.GetDriverByName("ESRI Shapefile")
    datasource = driver.CreateDataSource(dstFile)
    layer = datasource.CreateLayer(str(shapefile.filename),
                                   dstSpatialRef)

Note

Notice that we're using str() to convert the Shapefile's filename to an ASCII string. This is because Django uses Unicode strings, but OGR can't handle unicode filenames. We'll need to do the same thing for the attribute names.

Now that we've created the Shapefile itself, we next need to define the various fields which will hold the Shapefile's attributes:

    for attr in shapefile.attribute_set.all():
        field = ogr.FieldDefn(str(attr.name), attr.type)
        field.SetWidth(attr.width)
        field.SetPrecision(attr.precision)
        layer.CreateField(field)

Notice how the information needed to define the field is taken directly from the Attribute object; Django makes iterating over the Shapefile's attributes easy.

That completes the definition of the Shapefile. We're now ready to start saving the Shapefile's features.

Saving the features into the Shapefile

Because the Shapefile can use any valid spatial reference, we'll need to transform the Shapefile's features from the spatial reference used internally (EPSG 4326) into the Shapefile's own spatial reference. Before we can do this, we'll need to set up an osr.CoordinateTransformation object to do the transformation:

    srcSpatialRef = osr.SpatialReference()
    srcSpatialRef.ImportFromEPSG(4326)

    coordTransform = osr.CoordinateTransformation(srcSpatialRef, dstSpatialRef)

We'll also need to know which geometry field in the Feature object holds the feature's geometry data:

    geomField = utils.calcGeometryField(shapefile.geom_type)

With this information, we're ready to start exporting the Shapefile's features:

    for feature in shapefile.feature_set.all():
        geometry = getattr(feature, geomField)

Right away, however, we encounter a problem. If you remember when we imported the Shapefile, we had to wrap a Polygon or a LineString geometry into a MultiPolygon or MultiLineString so that the geometry types would be consistent in the database. Now that we're exporting the Shapefile, we need to unwrap the geometry so that features that had only one Polygon or LineString in their geometries are saved as Polygons and LineStrings rather than MultiPolygons and MultiLineStrings. We'll use a utils.py function to do this unwrapping:

        geometry = utils.unwrapGEOSGeometry(geometry)

We'll implement this utils.py function shortly.

Now that we've unwrapped the feature's geometry, we can go ahead and convert it back into an OGR geometry again, transform it into the Shapefile's own spatial reference system, and create an OGR feature using that geometry:

        dstGeometry = ogr.CreateGeometryFromWkt(geometry.wkt)
        dstGeometry.Transform(coordTransform)

        dstFeature = ogr.Feature(layer.GetLayerDefn())
        dstFeature.SetGeometry(dstGeometry)

Finally, we need to add the feature to the layer and call the Destroy() method to save the feature (and then the layer) into the Shapefile:

        layer.CreateFeature(dstFeature)
        dstFeature.Destroy()

    datasource.Destroy()

Before we move on, let's add our new unwrapGEOSGeometry() function to utils.py. This code is quite straightforward, pulling a single Polygon or LineString object out of a MultiPolygon or MultiLineString if they contain only one geometry:

def unwrapGEOSGeometry(geometry):
    if geometry.geom_type in ["MultiPolygon",
                              "MultiLineString"]:
        if len(geometry) == 1:
            geometry = geometry[0]
    return geometry

So far, so good: we've created the OGR feature, unwrapped the feature's geometry, and stored everything into the Shapefile. Now, we're ready to save the feature's attribute values.

Saving the attributes into the Shapefile

Our next task is to save the attribute values associated with each feature. When we imported the Shapefile, we extracted the attribute values from the various OGR data types and converted them into strings so they could be stored into the database. This was done using the utils.getOGRFeatureAttribute() function. We now have to do the opposite: storing the string value into the OGR attribute field. As before, we'll use a utils.py function to do the hard work; add the following highlighted lines to the bottom of your exportData() function:

        ...

        dstFeature = ogr.Feature(layer.GetLayerDefn())
        dstFeature.SetGeometry(dstGeometry)

        for attrValue in feature.attributevalue_set.all():
            utils.setOGRFeatureAttribute(attrValue.attribute,
                                         attrValue.value,
                                         dstFeature,
                                         shapefile.encoding)

        layer.CreateFeature(dstFeature)
        dstFeature.Destroy()

    datasource.Destroy()

Now, let's implement the setOGRFeatureAttribute() function within utils.py. As with the getOGRFeatureAttribute() function, setOGRFeatureAttribute() is rather tedious, but straightforward: we have to deal with each OGR data type in turn, processing the string representation of the attribute value and calling the appropriate SetField() method to set the field's value. Here is the relevant code:

def setOGRFeatureAttribute(attr, value, feature, encoding):
    attrName = str(attr.name)

    if value == None:
        feature.UnsetField(attrName)
        return

    if attr.type == ogr.OFTInteger:
        feature.SetField(attrName, int(value))
    elif attr.type == ogr.OFTIntegerList:
        integers = eval(value)
        feature.SetFieldIntegerList(attrName, integers)
    elif attr.type == ogr.OFTReal:
        feature.SetField(attrName, float(value))
    elif attr.type == ogr.OFTRealList:
        floats = []
        for s in eval(value):
            floats.append(eval(s))
        feature.SetFieldDoubleList(attrName, floats)
    elif attr.type == ogr.OFTString:
        feature.SetField(attrName, value.encode(encoding))
    elif attr.type == ogr.OFTStringList:
        strings = []
        for s in eval(value):
            strings.append(s.encode(encoding))
        feature.SetFieldStringList(attrName, strings)
    elif attr.type == ogr.OFTDate:
        parts = value.split(",")
        year  = int(parts[0])
        month = int(parts[1])
        day   = int(parts[2])
        tzone = int(parts[3])
        feature.SetField(attrName, year, month, day,
                         0, 0, 0, tzone)
    elif attr.type == ogr.OFTTime:
        parts  = value.split(",")
        hour   = int(parts[0])
        minute = int(parts[1])
        second = int(parts[2])
        tzone  = int(parts[3])
        feature.SetField(attrName, 0, 0, 0,
                         hour, minute, second, tzone)
    elif attr.type == ogr.OFTDateTime:
        parts = value.split(",")
        year   = int(parts[0])
        month  = int(parts[1])
        day    = int(parts[2])
        hour   = int(parts[3])
        minute = int(parts[4])
        second = int(parts[5])
        tzone  = int(parts[6])
        feature.SetField(attrName, year, month, day,
                         hour, minute, second, tzone)

Compressing the Shapefile

Note that we use a temporary file object, named temp, to store the ZIP archive's contents. Go back to the shapefileIO.py module and add the following to the end of your exportData() function:

    temp = tempfile.TemporaryFile()
    zip = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED)

    shapefileBase = os.path.splitext(dstFile)[0]
    shapefileName = os.path.splitext(shapefile.filename)[0]

    for fName in os.listdir(dstDir):
        zip.write(os.path.join(dstDir, fName), fName)

    zip.close()

Note that we use a temporary file, named temp, to store the ZIP archive's contents. We'll be returning temp to the user's web browser once the export process has finished.

Deleting temporary files

We next have to clean up after ourselves by deleting the Shapefile that we created earlier:

    shutil.rmtree(dstDir)

Notice that we don't have to remove the temporary ZIP archive as that's done automatically for us by the tempfile module when the file is closed.

Returning the ZIP archive to the user

The last step in exporting the Shapefile is to send the ZIP archive to the user's web browser so that it can be downloaded onto the user's computer. To do this, we'll create an HttpResponse object that includes a Django FileWrapper object to attach the ZIP archive to the HTTP response:

    f = FileWrapper(temp)
    response = HttpResponse(f, content_type="application/zip")
    response['Content-Disposition'] = 
        "attachment; filename=" + shapefileName + ".zip"
    response['Content-Length'] = temp.tell()
    temp.seek(0)
    return response

As you can see, we set up the HTTP response to indicate that we're returning a file attachment. This forces the user's browser to download the file rather than trying to display it. We also use the original Shapefile's name as the name of the downloaded file.

This completes the definition of the exportData() function. There's only one more thing to do: add the following import statements to the top of the shapefileIO.py module:

from django.http import HttpResponse
from django.core.servers.basehttp import FileWrapper

We've finally finished implementing the "Export Shapefile" feature. Test it out by running the server and clicking on the Export hyperlink beside one of your Shapefiles. All going well, there'll be a slight pause and you'll be prompted to save your Shapefile's ZIP archive to disk:

Returning the ZIP archive to the user
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.96.105