Table of Contents for
Python Geospatial Development - Third Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Python Geospatial Development - Third Edition by Erik Westra Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Python Geospatial Development Third Edition
  4. Python Geospatial Development Third Edition
  5. Credits
  6. About the Author
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Conventions
  13. Reader feedback
  14. Customer support
  15. 1. Geospatial Development Using Python
  16. Geospatial development
  17. Applications of geospatial development
  18. Recent developments
  19. Summary
  20. 2. GIS
  21. GIS data formats
  22. Working with GIS data manually
  23. Summary
  24. 3. Python Libraries for Geospatial Development
  25. Dealing with projections
  26. Analyzing and manipulating Geospatial data
  27. Visualizing geospatial data
  28. Summary
  29. 4. Sources of Geospatial Data
  30. Sources of geospatial data in raster format
  31. Sources of other types of geospatial data
  32. Choosing your geospatial data source
  33. Summary
  34. 5. Working with Geospatial Data in Python
  35. Working with geospatial data
  36. Changing datums and projections
  37. Performing geospatial calculations
  38. Converting and standardizing units of geometry and distance
  39. Exercises
  40. Summary
  41. 6. Spatial Databases
  42. Spatial indexes
  43. Introducing PostGIS
  44. Setting up a database
  45. Using PostGIS
  46. Recommended best practices
  47. Summary
  48. 7. Using Python and Mapnik to Generate Maps
  49. Creating an example map
  50. Mapnik concepts
  51. Summary
  52. 8. Working with Spatial Data
  53. Designing and building the database
  54. Downloading and importing the data
  55. Implementing the DISTAL application
  56. Using DISTAL
  57. Summary
  58. 9. Improving the DISTAL Application
  59. Dealing with the scale problem
  60. Performance
  61. Summary
  62. 10. Tools for Web-based Geospatial Development
  63. A closer look at three specific tools and techniques
  64. Summary
  65. 11. Putting It All Together – a Complete Mapping System
  66. Designing the ShapeEditor
  67. Prerequisites
  68. Setting up the database
  69. Setting up the ShapeEditor project
  70. Defining the ShapeEditor's applications
  71. Creating the shared application
  72. Defining the data models
  73. Playing with the admin system
  74. Summary
  75. 12. ShapeEditor – Importing and Exporting Shapefiles
  76. Importing shapefiles
  77. Exporting shapefiles
  78. Summary
  79. 13. ShapeEditor – Selecting and Editing Features
  80. Editing features
  81. Adding features
  82. Deleting features
  83. Deleting shapefiles
  84. Using the ShapeEditor
  85. Further improvements and enhancements
  86. Summary
  87. Index

Dealing with the scale problem

The preceding illustration reveals a second problem with the DISTAL system: because the USA including Alaska is over 4,000 miles wide, accurately selecting a 10-mile search radius by clicking on a point on this map would be an exercise in frustration.

To solve this problem, we will implement a zoom feature so that the user can click more accurately on the desired starting point. Because the DISTAL system is implemented as a series of CGI scripts, our zoom feature is going to be rather basic: if the user holds down the Shift key while clicking, we zoom in on the clicked-on point. If the Shift key is not held down when the user clicks, we proceed with the search as usual.

Note

In a real web application, we would implement a complete slippy map interface that supports click-and-drag as well as on-screen controls to zoom both in and out. Doing this is way beyond what we can do with simple CGI scripts, however. We will return to the topic of slippy maps in Chapter 10, Tools for Web-based Geospatial Development.

To implement zooming, we will need to update the selectArea.py script we wrote earlier. We will make use of some rudimentary JavaScript code to detect whether the user held down the Shift key and, if so, reload the "select area" page with additional CGI parameters that allow us to zoom in. In particular, we are going to modify our CGI script to accept the following additional parameters:

CGI parameter(s)

Description

x and y

The coordinates of the point on which the user clicked.

zoom

The zoom level to use, where 0 equals no zoom.

minLat, minLong, maxLat, maxLong

The lat/long bounding box that was used to draw the map at the previous zoom level.

All of these parameters are optional—if they are not supplied, default values will be calculated.

Let's modify our script to use these parameters to zoom in on the map. Start by deleting the block of code that calculated the lat/long bounding box, that is, the lines of code starting with the cursor.execute("SELECT name, ...") statement and ending with the line maxLat = maxLat + 0.2. Replace these with the following code:

if "x" in form:
    click_x = int(form['x'].value)
else:
    click_x = None

if "y" in form:
    click_y = int(form['y'].value)
else:
    click_y = None

if "zoom" in form:
    zoom = int(form['zoom'].value)
else:
    zoom = 0

if ("minLat" in form and "minLong" in form and
    "maxLat" in form and "maxLong" in form):
    # Use the supplied bounding box.
    minLat  = float(form['minLat'].value)
    minLong = float(form['minLong'].value)
    maxLat  = float(form['maxLat'].value)
    maxLong = float(form['maxLong'].value)
else:
    # Calculate the bounding box from the country outline.

    cursor.execute("SELECT " +
            "ST_YMin(ST_Envelope(outline))," +
            "ST_XMin(ST_Envelope(outline))," +
            "ST_YMax(ST_Envelope(outline))," +
            "ST_XMax(ST_Envelope(outline)) " +
            "FROM countries WHERE id=%s", (countryID,))

    row = cursor.fetchone()
    if row != None:
        minLat  = row[0]
        minLong = row[1]
        maxLat  = row[2]
        maxLong = row[3]
    else:
        print(HEADER)
        print('<b>Missing country</b>')
        print(FOOTER)
        sys.exit(0)

    minLong = minLong - 0.2
    minLat = minLat - 0.2
    maxLong = maxLong + 0.2
    maxLat = maxLat + 0.2

# Get the country's name.

cursor.execute("SELECT name FROM countries WHERE id=%s",
               (countryID,))
name = cursor.fetchone()[0]

As you can see, we extract the x, y, and zoom CGI parameters, setting them to default values if they are not supplied. We then check whether the minLat, minLong, maxLat, and maxLong parameters are supplied. If not, we ask the database to calculate these values based on the country's outline and then add a margin of 0.2 degrees to each one. Finally, we load the name of the country from the database.

Now that we have our CGI parameters and are still calculating default values when required, we can use these parameters to zoom in on the map as required. Fortunately, this is quite easy: we simply modify the bounding box to show a smaller area of the world if the user has zoomed in. To do this, add the following code immediately before the hilite = "[id] = " + str(countryID) line:

if zoom != 0 and click_x != None and click_y != None:
    xFract = float(click_x)/float(mapWidth)
    longitude = minLong + xFract * (maxLong-minLong)

    yFract = float(click_y)/float(mapHeight)
    latitude = minLat + (1-yFract) * (maxLat-minLat)

    width = (maxLong - minLong) / (2**zoom)
    height = (maxLat - minLat) / (2**zoom)

    minLong = longitude - width / 2
    maxLong = longitude + width / 2
    minLat = latitude - height / 2
    maxLat = latitude + height / 2

In this section of the script, we first calculate the latitude and longitude of the point where the user clicked. We will zoom in on this point. We then use the zoom level to calculate the width and height of the area to display, and we then recalculate the bounding box so that it has the given width and height but is centered on the clicked-on point.

We next need to write the JavaScript code to detect when the user clicks with the Shift key held down, and call our script again with the appropriate parameters. To do this, replace the HEADER definition near the top of the file with the following:

HEADER = "\n".join([
    "Content-Type: text/html; charset=UTF-8",
    "",
    "",
    "<html><head><title>Select Area</title>",
    "<script type='text/javascript'>",
    "  function onClick(e) {",
    "    e = e || window.event;",
    "    if (e.shiftKey) {",
    "      var target = e.target || e.srcElement;",
    "      var rect = target.getBoundingClientRect();",
    "      var offsetX = e.clientX - rect.left;",
    "      var offsetY = e.clientY - rect.top;",
    "      var countryID = document.getElementsByName('countryID')[0].value;",
    "      var minLat = document.getElementsByName('minLat')[0].value;",
    "      var minLong = document.getElementsByName('minLong')[0].value;",
    "      var maxLat = document.getElementsByName('maxLat')[0].value;",
    "      var maxLong = document.getElementsByName('maxLong')[0].value;",
    "      var zoom = document.getElementsByName('zoom')[0].value;",
    "      var new_zoom = parseInt(zoom, 10) + 1;",
    "      window.location.href = 'selectArea.py'",
    "                           + '?countryID=' + countryID",
    "                           + '&minLat=' + minLat",
    "                           + '&minLong=' + minLong",
    "                           + '&maxLat=' + maxLat",
    "                           + '&maxLong=' + maxLong",
    "                           + '&zoom=' + new_zoom",
    "                           + '&x=' + offsetX",
    "                           + '&y=' + offsetY;",
    "      return false;",
    "    } else {",
    "      return true;",
    "    }",
    "  }",
    "</script>",
    "</head><body>"])

We won't go into the details of this code, as writing JavaScript is beyond the scope of this book. In this JavaScript code, we define a function called onClick() which checks to see whether the user held down the Shift key and, if so, calculates the various CGI parameters and sets window.location.href to the URL used to reload the CGI script with those parameters.

There are just two more things we need to do to complete our new zoom feature: we need to add a new hidden form field to store the zoom value, and we need to call our onClick() function when the user clicks on the map. Add the following line to the list of print(HIDDEN_FIELD.format(...)) statements near the bottom of the script:

print(HIDDEN_FIELD.format("zoom", zoom))

Finally, change the print('<input type="image"...>') line to the following:

print('<input type="image" src="' + imgFile + '" ismap ' +
      'onClick="return onClick()">')

This completes the changes required to support zooming. If you now run the DISTAL program and select a country, you should be able to hold down the Shift key while clicking to zoom in on the map. This should make it much easier to select the desired point within a larger country.

Tip

To zoom back out again, click on the Back button in your web browser.

We have now corrected the two major usability issues in the DISTAL system. There is, however, one other area we need to look at: performance. Let's do this now.