Chapter 6. Manipulating geometries with OGR

This chapter covers

  • Creating points, lines, and polygons from scratch
  • Editing existing geometries

Thus far we’ve talked about using OGR to read and write vector datasets and how to edit attribute values, but you haven’t manipulated the geometries in any way. If you want to create your own data and not use someone else’s, you’ll need to know how to work with the actual geometries. For example, if you have a time series of GPS coordinates from a hiking or bicycling trip, you can create a line that represents the route you took. You can even compare the timestamps from the GPS locations to the timestamps on the photos you took to create a point dataset showing where you stopped to take pictures.

You might even need to know how to manipulate geometries to better display existing data. For example, say you want to create a map using your photo points and link them to the actual photos. Certain locations will probably have multiple photos in the same spot. You can deal with this many ways, but one way is to offset each point a little in a different direction so that it looks like a cluster of points instead of only one. But to do this, you need to know how to manipulate the point geometries themselves.

You can also manipulate and combine geometries to create new ones. For example, if you want to create a simple map of riparian areas from a stream dataset, and you assume that the riparian zones stretch one meter on either side of a stream, you can create a polygon that surrounds each stream, with the edges of the polygon one meter out on each side of the water. If two streams join, then these polygons will overlap near their confluence, and you can combine the overlapping polygons into one using a union operation. You’ll learn how to do all of this, and more, in the next two chapters.

Before you can do any of this, however, you need to be acquainted with the different types of geometries.

6.1. Introduction to geometries

You have several kinds of geometries with which you can work, points being the simplest. All other types are made up of points connected by straight line segments, and the points are what store the coordinate values, so points can be thought of as the building blocks of geometries. These points that are used to build other geometries are called vertices, and there can be thousands of vertices per geometry, if needed. For example, line geometries are an ordered collection of points that are connected by straight line segments, with a vertex at every location where the line needs to change direction. A line representing a short dead-end street wouldn’t require many vertices, but one representing the Amazon River in much detail would need thousands. Polygons are somewhat similar to lines, but they’re closed, meaning the first and last vertex are identical and they enclose a specific area. You’ll start with creating and editing points and work your way up to the more complicated geometries as you go along.

Definition

A vertex is a point where two line segments of a geometry meet. Vertices hold coordinates for the ends of each line segment.

Although many geometries live only in a two-dimensional (2D) Cartesian coordinate plane with x and y coordinates, it’s also possible to have three-dimensional (3D) geometry objects with z values. These z values are typically used to represent elevation, but can also be used for other data, such as maximum annual temperature. Technically, these geometries are considered 2.5D instead of 3D in OGR, because OGR doesn’t take the z values into account when performing spatial operations. One thing to be aware of is that although you can add z values to 2D geometries, they’ll be ignored when writing the data to a file.

Note

Geometries with only x and y coordinates are considered 2D. Geometries with an additional z coordinate are considered 2.5D instead of 3D in OGR because the z values aren’t taken into account when performing spatial operations.

It’s probably easiest to work with simple geometries when you’re beginning, so you’ll learn to create different geometry types by re-creating the fictional yard shown in figure 6.1. If you use your imagination when looking at this figure, hopefully you can envision a yard with a house in the middle, rectangular garden beds to the east, a sidewalk on the north side (solid line), stone pathways (dotted lines), a fire pit (star), and outdoor water spigots (circles). Although you’ll create this scenario in two-dimensional space, the same concepts apply to 2.5D geometries.

Figure 6.1. The fictional yard whose geometries you’ll create throughout this chapter. Coordinates are in meters.

Although the shapes shown in figure 6.1 are simple, the concepts are exactly the same as working with complex geometries. You can apply the material you learn here to real-world scenarios.

6.2. Working with points

Points consist of an east/west x coordinate, a north/south y coordinate, and sometimes a vertical z coordinate that’s commonly used for elevation. You’re probably familiar with the x coordinate being called longitude and the y coordinate called latitude. These terms are appropriate when a geographic coordinate system is being used, where latitude ranges from -90 to 90 and longitude is between -180 and 180. If the coordinates have been projected into a Cartesian coordinate system, such as UTM, then common terms are easting for the x coordinate and northing for the y.

Points are used to represent items that have only one set of coordinates. Points don’t have a length, width, area, or any other measurement. Despite this, the features represented by points on a map vary depending on scale, and those features might have an area in real life. For example, a map of France would most likely represent Paris as a single point, while a map of the Île-de-France region would show more detail, with the Paris city boundaries represented as polygons and major landmarks such as the Eiffel Tower shown as points. As the scale changes, the areas of features represented by points will also change, similar to the way the area covered by the Eiffel Tower is much smaller than that covered by Paris.

6.2.1. Creating and editing single points

Looking at the yard diagram, you can see that the fire pit is a perfect candidate to be represented as a single point, so let’s build it. Figure 6.2 shows a close-up of the area so that you can see the coordinates.

Figure 6.2. You can use a single point to hold the fire pit geometry, shown here as a star.

Unless you have a text representation of the geometry you want to build, the first step to building any type of geometry with OGR is to create an empty Geometry object using one of the constants from table 6.1. Go ahead and do this in an interactive window so that you can get immediate results:

>>> firepit = ogr.Geometry(ogr.wkbPoint)
Table 6.1. OGR constants denoting geometry types

Geometry type

2D constant

2.5D constant

Point wkbPoint wkbPoint25D
Multipoint wkbMultiPoint wkbMultiPoint25D
Line wkbLineString wkbLineString25D
Multiline wkbMultiLineString wkbMultiLineString25D
Polygon ring wkbLinearRing n/a
Polygon wkbPolygon wkbPolygon25D
Multipolygon wkbMultiPolygon wkbMultiPolygon25D
Geometry collection wkbGeometryCollection wkbGeometryCollection25D

Once you have the geometry, you can start adding vertices. Points only have one vertex, which you add with the AddPoint function. This function wants x, y, and an optional z.

>>> firepit.AddPoint(59.5, 11.5)

That’s it! You now have a fully functional point object with a northing of 11.5 and an easting of 59.5. The coordinates can be retrieved if needed using GetX, GetY, and GetZ:

x, y = firepit.GetX(), firepit.GetY()

Remember that in Python you can set multiple variables at once, so x is assigned the results of GetX and y gets the results of GetY.

You can also print geometry objects in WKT format if you want to verify that things look okay, although this can get ugly pretty fast with geometry types other than points.

>>> print(firepit)
POINT (59.5 11.5 0)

Notice that the WKT shows a z value of 0, but you created a 2D point. That’s not going to hurt anything, so there’s no reason to worry about it. You could even set a z value yourself, although it would be ignored when it came time to write the geometry out to a file.

Unless you want to see the coordinate values, an easier way to visualize your geometries as you create them is to use the VectorPlotter class that we introduced in chapter 3, although this is boring with a single point:

>>> vp.plot(firepit, 'bo')

What if you realize later that your GPS was slightly off and the y coordinate is 13 instead of 11.5? The easiest way to solve the problem is to call AddPoint again, but with the correct coordinates. You can verify the results by plotting the new geometry with a different marker (figure 6.3) or by printing the WKT:

>>> firepit.AddPoint(59.5, 13)
>>> vp.plot(firepit, 'rs')
>>> print(firepit)
POINT (59.5 13.0 0)
Figure 6.3. The original and edited fire pit geometries. The edited one has the square marker.

Why doesn’t this add a second set of coordinates to the geometry, as the name AddPoint implies? Points are a special case because they’re only allowed one set of coordinates, so any existing ones are overwritten. You’ll see later that AddPoint has different behavior when applied to other geometry types.

If you like to make life a little more complicated, or want to be more consistent with how vertices are edited with other geometry types, you can use SetPoint(point, x, y, [z]) instead, where point is the index of the vertex to edit. Because point geometries contain only one vertex, this parameter is always zero when dealing with points:

firepit.SetPoint(0, 59.5, 13)

To create a 2.5D point, specify the 2.5D type when you create it and then provide a z coordinate along with the x and y:

firepit = ogr.Geometry(ogr.wkbPoint25D)
firepit.AddPoint(59.5, 11.5, 2)

Other than the addition of a third coordinate value, working with 2.5D points is the same as working with 2D points.

6.2.2. Creating and editing multipoints: multiple points as one geometry

Multipoint geometries contain one or more points in a single object. This means that multiple points can be attached to a single feature rather than requiring a separate feature per point. For example, a dataset with multiple points might be the locations of all fire hydrants within city boundaries, where each hydrant is treated as a unique feature. Perhaps you also want to map outdoor water faucets in private yards. In this case, you might treat all spigots in an individual yard as one multipoint item so that you have only one feature per yard. In fact, that’s what you’ll do with the yard example. Three faucets are shown as circles in figure 6.4, and you’ll build one multipoint object with three vertices to represent them.

Figure 6.4. You can use a multipoint geometry to hold the water spigot geometries, shown here as dots.

To create a multipoint geometry, you need to create at least two geometries. You need at least one point object and also a multipoint object to hold the points. Referring back to table 6.1, you can see that the correct OGR constant for a multipoint object is wkbMultiPoint. You create the points exactly as before and then add them to your multipoint geometry. Here’s one way to do this, using coordinates obtained from figure 6.4:

Notice that it’s fine to reuse the same point geometry each time. A copy of the point object is added to the multipoint when AddGeometry is invoked, so the original point can be edited later without affecting the coordinates that have already been added to the multipoint. You could, of course, create a new point object for each vertex, but reusing the geometry saves a little overhead.

Once again, you can plot the geometry and print the WKT to see what it looks like:

With a multipoint object, the WKT string separates each coordinate in the set with a comma. As with a regular point object, the x, y, and z coordinates for a single point are separated by spaces. Notice also that the vertices are listed in the same order you added them. That’s extremely important if you need to access one later—you can always be sure of which point you’re getting because their order doesn’t change.

You can get a specific point from a multipoint geometry by passing the index of the desired point to GetGeometryRef. The first point added has index 0, the second has index 1, and so on. Once you have an individual point, you can edit it the same way as a single point. Because GetGeometryRef returns a reference to the point inside the multipoint instead of a copy, the multipoint is automatically updated when the point is changed. For example, this would get the second faucet and then edit its coordinates:

faucets.GetGeometryRef(1).AddPoint(75, 32)

You can also find out how many points are in a multipoint object, which is useful if you need to loop through them all. For example, to move all spigots two meters to the east, you’d need to loop through the points and add 2 to each x coordinate while leaving the y coordinates unchanged. The results are shown in figure 6.5.

Figure 6.5. The original and edited water spigot multipoint geometries. The edited geometry uses square markers.

>>> for i in range(faucets.GetGeometryCount()):
...     pt = faucets.GetGeometryRef(i)
...     pt.AddPoint(pt.GetX() + 2, pt.GetY())
...
>>> vp.plot(faucets, 'rs')
>>> vp.zoom(-5)

As you’ll see in the rest of this chapter, working with other geometry types directly builds on these concepts that you’ve learned for points.

6.3. Working with lines

As mentioned earlier, lines are a sequence of vertices, or points, connected by straight line segments. Figure 6.6 shows a line with its vertices, although normally you don’t see markers for the vertices when a line is drawn. A line can’t change direction, no matter how slightly, without a vertex to end one segment and start another. Therefore, a line that looks like a smooth curve is a large number of short straight segments, all joined together by vertices.

Figure 6.6. A line and the vertices connecting each segment

Adding more vertices, and therefore a larger number of shorter segments, gives you more control over the shape of the line. Think about how you’d draw the coastline of Great Britain using a series of straight lines. As you can see from figure 6.7, accuracy is greatly improved by using shorter lines. The same concept applies to any line geometry. The more detail required, the more vertices you need to add. Keep in mind that the more vertices you have, the more complicated the geometry object is and the more time it takes to process, so don’t add unnecessary vertices. In fact, you might want to simplify geometries so that they use fewer vertices if you’re going to serve data over the web. See the Simplify function in appendix C if you need to do this. (Appendixes C through E are available online on the Manning Publications website at https://www.manning.com/books/geoprocessing-with-python.)

Figure 6.7. The solid line follows the coast of Great Britain more closely because it has more vertices, and therefore more and shorter line segments, than the dotted line. More detail for lines and polygons can be achieved by using more vertices.

Lines can be used to represent linear features such as roads, streams, or pipelines. A line is a good choice if you want to show one coastline of an island, such as the example in figure 6.7, but a polygon is a better choice if you want to represent the entire island. You’ll use a simple line object to model the sidewalk bordering the make-believe yard and the oddly shaped parking strip (figure 6.8).

Figure 6.8. You can use a line to hold the sidewalk geometry, shown here as the thick green line.

The line you’ll build for the sidewalk contains a small number of vertices, but the technique for working with longer and more-complex lines is exactly the same.

6.3.1. Creating and editing single lines

As with points, the first step to creating a line geometry is to create an empty Geometry object and then add the vertices. Although the direction you traverse the line when adding coordinates isn’t important, the vertices must be added in order. Try creating the line representing the sidewalk shown in figure 6.8, going from west to east:

Remember how AddPoint overwrote existing coordinates in point objects? That doesn’t happen here because lines consist of many vertices instead of only one, so a new vertex is added to the end of the line instead of overwriting the only allowed point.

Again, you can verify that things are working as expected by plotting the geometry or printing the WKT:

>>> vp.plot(sidewalk, 'b-')
>>> print(sidewalk)
LINESTRING (54 37 0,62.0 35.5 0,70.5 38.0 0,74.5 41.5 0)

As before, the coordinates for a vertex are separated by spaces, and individual vertices are separated by commas. Because you know that the vertices are always in the same order that they were added to the line, you could use SetPoint to change the x coordinate for the last vertex (the one with index 3) in the sidewalk:

sidewalk.SetPoint(3, 76, 41.5)

You can find out how many vertices a line contains and then loop through all of them if necessary. For example, if you suddenly realize that the sidewalk is one meter too far south, you can nudge it north by looping through all of the vertices and adding one to each y coordinate (figure 6.9):

>>> for i in range(sidewalk.GetPointCount()):
...     sidewalk.SetPoint(i, sidewalk.GetX(i), sidewalk.GetY(i) + 1)
...
>>> vp.plot(sidewalk, 'r--')
Figure 6.9. The original and edited sidewalk line geometries. The edited one is shown with the dotted line.

Notice that you use GetX and GetY again, but now you need to provide the index of the vertex you want. You also might wonder why you use GetPointCount instead of GetGeometryCount as you did for multipoints, and that’s a good question. The reason is because GetGeometryCount tells you how many individual geometry objects are combined to make up one multigeometry, and it returns zero if the object isn’t a multigeometry. The GetPointCount function, on the other hand, returns the number of vertices in a geometry, and it returns zero for multigeometries because they’re made of other geometries instead of vertices.

Tip

Use GetGeometryCount to determine the number of geometries contained in a collection of geometries (such as a multigeometry or a polygon), and use GetPointCount to determine the number of vertices in a single geometry. The GetGeometryCount function will always return zero for a single geometry, and GetPointCount will always return zero for a collection of geometries.

What if you later realize that the shape of the sidewalk is all wrong because it’s missing a vertex? If you still have the coordinates for each vertex, then the easiest thing is probably to create a new sidewalk geometry using all of the vertices. But if your landscaper only gave you the coordinates of the missing vertex and told you that it needed to be inserted between the second and third vertices, as shown in figure 6.10, you’d need to insert it in your line. You can solve this problem multiple ways, but I think the easiest is to get a list of all the vertices, insert a new set of coordinates into the list, and then use the list to create a new geometry. You can use GetPoints to get the list of vertices in a line, where each vertex is in the form of a tuple with x, y, and z coordinates. Here’s what that list looks like for the original sidewalk:

>>> print(sidewalk.GetPoints())
[(54.0, 37.0, 0.0), (62.0, 35.5, 0.0), (70.5, 38.0, 0.0),
 (74.5, 41.5, 0.0)]
Figure 6.10. The dotted line shows changes to make to the sidewalk by inserting a new vertex between the existing second and third vertices.

A handy feature of lists is that you can easily insert items into the ith position using the list[i:i] syntax. The following example gets the list of sidewalk vertices and then inserts a tuple containing new x and y coordinates in between the second and third vertices:

>>> vertices = sidewalk.GetPoints()
>>> vertices[2:2] = [(66.5, 35)]
>>> print(vertices)
[(54.0, 37.0, 0.0), (62.0, 35.5, 0.0), (66.5, 35),
 (70.5, 38.0, 0.0), (74.5, 41.5, 0.0)]

You can see that the original vertices are still there, but the new vertex has been inserted at index 2. This vertex doesn’t have a z coordinate because one wasn’t provided in the inserted tuple. The fact that the original coordinates do have z values is unimportant, because this is a 2D geometry and they should all be zero anyway.

Now you have a list of tuples, but how do you turn it into a line geometry, like that in figure 6.11? The easiest way is to take advantage of the Python * operator to expand the tuples to individual parameters and pass them in turn to AddPoint:

>>> new_sidewalk = ogr.Geometry(ogr.wkbLineString)
>>> for vertex in vertices:
...     new_sidewalk.AddPoint(*vertex)
...
>>> vp.plot(new_sidewalk, 'g:')
Figure 6.11. The original sidewalk line geometry and one with another vertex inserted in the middle

The Python * operator

The * operator unpacks the contents of a tuple or list into separate items so they can be passed as parameters to a function. Take this example:

Using the * operator explodes vertex into two parameters that are successfully passed to AddPoint. Forgetting the * operator only passes one parameter, a tuple. But AddPoint expects at least two parameters, an x and a y, so it fails.

This is easy to expand to a larger number of edits if needed. For example, what if you want to add new points after the existing 5th, 11th, 19th, and 26th vertices, as shown by the dotted line in figure 6.12? As you look at the following code, see if you can figure out why you’d want to add the vertices at the end of the line first:

Figure 6.12. The results of inserting multiple vertices into a line geometry. The original is shown with a solid line, and the edited is drawn as a dotted line.

Once you insert an item into a list, then the indices of the later items are changed. If you insert the points after the 5th vertex first, then the original 11th vertex will now have an index of 13, because two points were added earlier in the list. You’ll have to keep track of how many items you inserted so that you can get the later indices right. That’s certainly doable, but why bother if you can avoid the problem altogether by working backward?

Tip

If you need to insert or delete multiple items in a list (whether the list contains vertices or something else), you’ll find that life is easier if you start from the end and work backward so you don’t inadvertently change indexes that you still need to use.

If you don’t want to create a new line geometry, you can modify the original instead. This adds one vertex to the sidewalk line without creating a copy:

vertices = sidewalk.GetPoints()
vertices[2:2] = [(66.5, 35)]
for i in range(len(vertices)):
    sidewalk.SetPoint(i, *vertices[i])

But this uses SetPoint to edit five vertices when the original sidewalk only has four. How can you possibly change a vertex that doesn’t exist? It turns out that SetPoint will add a vertex at the requested index, along with any missing vertices in between. For example, if the line has ten vertices (so the highest index is 9) and you use SetPoint to create a vertex with index 15, it will also create vertices with indices 10 through 14. Be careful, though, because any vertices it adds as fillers are initialized to (0, 0), which is probably not what you want.

Creating points from lines

Sometimes you need to get the vertices of a line as individual points. By this time, you know how to create points and also how to manipulate individual line vertices, so turning the vertices into points shouldn’t be too difficult. All you need to do is loop through the line vertices, get the coordinates, and create a point using those coordinates. The following listing shows a function that does this.

Listing 6.1. Function to create a point layer from a line layer

This function takes a data source, the name of an existing line layer, and the name of a new point layer. It creates the point layer and copies all of the attribute fields from the line to the point layer. Then it loops through all of the line features and creates a point feature for each vertex, which also contains the same attribute values as the line it came from.

6.3.2. Creating and editing multilines: multiple lines as one geometry

As with multipoints, multiline objects contain one or more lines that are treated as if they were one. The collection of channels in a braided river is a good candidate for this geometry type. As shown in figure 6.13, you can also treat the stone pathways running through the yard as a multiline object.

Figure 6.13. You can use a multiline to hold the garden path geometries, shown here as dotted lines.

As with any multigeometry, you need to create each component separately and then add it to the main geometry, so you need at least one regular line object along with your multiline. The following code shows how to create the multiple paths from figure 6.13:

This is similar to creating a multipoint. The first thing you do is create the three separate line geometries that make up the pathways. After creating those, you create a multiline geometry and add the paths in order. It doesn’t matter if you wait until all of the individual lines are created and then create the multiline, or if you create the multiline up front and add the individual lines as you go along. You can even reuse one line object for each path, but you’d need to add the path to the multiline immediately after adding its vertices, and call Empty on the path geometry to clear out the old vertices before starting on the next pathway.

Let’s take a look at the guts of your new multiline:

>>> vp.plot(paths)
>>> print(paths)
MULTILINESTRING ((61.5 29.0 0,63 20 0,62.5 16.0 0,60 13 0),
 (60.5 12.0 0,68.5 13.5 0),(69.5 33.0 0,80 33 0,86.5 22.5 0))

Each inner line is inside its own set of parentheses, and they’re listed in the same order as they were added to the multiline. Again, it’s important that OGR preserves this order so that you always know which line is which.

To edit a vertex once it’s been added to the multiline, you first need to grab the single line that you want to edit, the same way you did with multipoints. Once you have that, you can edit the vertices the same way you would a regular line. To edit the second vertex in the first path added to the multiline, you can do something like this:

paths.GetGeometryRef(0).SetPoint(1, 63, 22)

You can use the concepts you’ve already learned about getting inner geometries and editing lines to move the whole multiline two units to the east and three to the south, with the results shown in figure 6.14:

Figure 6.14. The original and edited pathway multiline geometries. The edited one is drawn as a dotted line.

Hopefully you feel comfortable constructing and editing lines by this point, and you’ll see in the next section that working with polygons is only slightly more complicated.

6.4. Working with polygons

Polygons are used to represent things that have area, unlike a point or line. City boundaries and lakes are two examples of data that could be modeled as polygons. Instead of a polygon being made up of a list of vertices, like a line, they’re made of rings. This is because polygons can have holes in them, like a donut, and a separate ring is required for the outer polygon and for each of the holes. A simple polygon with no holes in it is still made up of one ring. Like lines, rings are made up of a series of vertices connected by straight line segments, but the first and last vertices are the same so that they form a closed ring.

Like lines, ring vertices need to be added in order, but you have other considerations as well. The line segments making up a polygon’s perimeter shouldn’t touch or cross, as shown in figure 6.15. OGR will allow you to create a polygon like this, but calculations on it are apt to be wrong, even if they run without errors. You can check for problems like this by calling IsValid on a geometry, which you should make a habit of if you’re building your own geometries. Note that polygons that don’t have a width—so they look like a line—are also invalid.

Figure 6.15. The polygon on the left is valid, but the other two are not because the line segments intersect and split the polygon.

Returning to the yard example, let’s start by creating a polygon for the entire yard boundary (figure 6.16).

Figure 6.16. You can use a polygon to hold the yard boundary, shown here as the thick solid line.

Once again, you’ll work with simple polygons in the examples, but your new knowledge can be easily applied to more-complex geometries.

6.4.1. Creating and editing single polygons

A polygon is like a multigeometry in the sense that it consists of a set of geometries. All polygons are made of rings, which in turn are made of vertices. A simple polygon only has one ring, but you still need to create a ring object and then add it to the polygon. As with lines, use AddPoint to add a vertex to a ring. Vertices need to be added in order, but the direction around the perimeter can vary depending on the format you want to use to store the data. For example, shapefiles specify that the outer rings are in clockwise order, but GeoJSON doesn’t specify an order. Because of details like this, it’s probably a good idea to read up about the format you intend to use. But no matter the direction, the first and last vertices must have the same coordinates so they close the ring. To do this, you can either make sure the last vertex added has identical coordinates to the first one, or you can call CloseRings on the ring or polygon after adding all vertices. The latter method is the one used here to create the yard outline shown in figure 6.16. The example starts with the upper left vertex and traverses the perimeter in counter-clockwise direction.

You can make sure things look okay by plotting the geometry and printing the WKT:

>>> vp.plot(yard, fill=False, edgecolor='blue')
>>> print(yard)
POLYGON ((58.0 38.5 0,53 6 0,99.5 19.0 0,73 42 0,58.0 38.5 0))

The WKT contains all of the coordinates for the ring, but notice that the coordinate list is inside a second set of parentheses. This is because the vertices make up a ring inside the polygon. You’ll see later there can be more than one ring within a polygon, which is why the ring needs to be delineated with its own set of parentheses.

If you were to call GetPointCount on yard, the response would be zero because the vertices belong to the ring that’s inside the polygon. This is similar to how multigeometries won’t admit to having vertices, but they’ll confess to containing other geometries. The yard variable would claim to have one geometry if you queried it with GetGeometryCount, and that one geometry is the ring. Because of this, to edit a polygon’s vertices you need to get the ring first, and then edit the ring the same way you edit lines. This example grabs the ring and shifts it five map units to the west, which automatically moves the whole polygon, as seen in figure 6.17:

>>> ring = yard.GetGeometryRef(0)
>>> for i in range(ring.GetPointCount()):
...     ring.SetPoint(i, ring.GetX(i) - 5, ring.GetY(i))
...
>>> vp.plot(yard, fill=False, ec='red', linestyle='dashed')
Figure 6.17. The original and edited yard polygon geometries. The original is drawn with a solid line.

You can insert vertices into polygon rings using the same method that you used for lines. For example, you can cut one of the sharp corners off of the yard by getting the ring and replacing the third vertex with two different vertices (figure 6.18):

>>> ring = yard.GetGeometryRef(0)
>>> vertices = ring.GetPoints()
>>> vertices[2:3] = ((90, 16), (90, 27))
>>> for i in range(len(vertices)):
...     ring.SetPoint(i, *vertices[i])
...
>>> vp.plot(yard, fill=False, ec='black', ls='dotted', linewidth=3)
Figure 6.18. The original yard geometry and one with the third vertex replaced with two other vertices.

Warning

Creating a linestring with the same beginning and ending vertices won’t create a ring that can be used to build a polygon. Instead, it will be a line that happens to stop at the same place it started. It still won’t have an area, perimeter, or any other properties specific to polygons.

Creating lines from polygons

Sometimes you need to convert polygons to lines. To do this, you need to create a line from each ring inside the polygon. I’ve had luck copying the rings into line features, but the following listing shows how to do it by creating a line from a ring instead. Similarly to the line_to_point_layer function in listing 6.1, this function requires a data source, the name of the existing polygon layer, and the name for a new line layer. It creates a new line layer with the same attributes as the polygon layer, and then for each polygon feature, copies each ring to a line and inserts a new feature in the line layer.

Listing 6.2. Function to create a line layer from a polygon layer

After creating a new layer to hold the lines, the function starts iterating through the polygons in the original layer, and creates a new line for each ring in the polygon. To do this, each time it finds a ring, it creates an empty line object and then iterates through the ring’s vertices. The coordinates for each ring vertex are used to create a new vertex in the line, so you get a line containing all of the same vertices as the ring. Lines, however, aren’t closed even if the first and last vertices are the same. If you plot the line, it will look like a polygon, but it doesn’t have the concept of an area or anything else specific to polygons.

6.4.2. Creating and editing multipolygons: multiple polygons as one geometry

If you’ve read this far, you can guess that a multipolygon is a geometry made of one or more individual polygons. A classic example of this is the Hawaiian Islands. This archipelago makes up the state of Hawaii and is usually represented as one geometry in datasets covering the United States, but it’s obviously made of several islands. The collection of islands makes up one state, the same way a collection of polygons makes up one multipolygon. An example is shown in figure 6.19.

Figure 6.19. You can use a multipolygon to hold the garden boxes, shown here as the two rectangles.

You can probably also figure out how to create a multipolygon, because you don’t need to know anything new. You create the individual polygons and add them to a multipolygon, and that’s all there is to it. For example, the following listing shows how you can treat the garden boxes in figure 6.19 as a multipolygon made of two individual raised beds.

Listing 6.3. Creating a multipolygon

Let’s look at the WKT for this multipolygon:

>>> vp.plot(gardens, fill=False, ec='blue')
>>> print(gardens)
MULTIPOLYGON (((87.5 25.5 0,89.0 25.5 0,89 24 0,87.5 24.0 0,87.5 25.5 0)),
 ((89 23 0,92 23 0,92 22 0,89 22 0,89 23 0)))

Here you have two polygons inside of a multipolygon, each inside its own set of parentheses, and each one of these contains one ring in another set of parentheses. Once again, everything is in the same order that you added it.

Editing a multipolygon is similar to what you’ve already seen, although it has one more step because you need to get each inner polygon and then get the ring from that before you can edit vertices. Figure 6.20 shows the result of moving the garden boxes one map unit to the east and half a unit to the north.

Figure 6.20. The original and edited garden box multipolygon geometries. The edited one is drawn with a dashed line.

>>> for i in range(gardens.GetGeometryCount()):
...     ring = gardens.GetGeometryRef(i).GetGeometryRef(0)
...     for j in range(ring.GetPointCount()):
...         ring.SetPoint(j, ring.GetX(j) + 1, ring.GetY(j) + 0.5)
...
>>> vp.plot(gardens, fill=False, ec='red', ls='dashed')

Now you know how to work with single and multi-geometries, but you still have the special case of polygons with holes. Keep reading to learn how these are different than multipolygons.

6.4.3. Creating and editing polygons with holes: donuts

What about polygons with holes in them, like donuts? These are different than multipolygons, because the hole is the absence of a polygon, not a second polygon. This is why polygons need to be made of rings. One ring defines the outer edge of the donut, and another delineates the hole. You need to add the outer ring to the polygon first, and subsequent rings define holes in the geometry. To illustrate how to do this, try cutting the house out of the yard polygon (figure 6.21).

Figure 6.21. You can use a polygon with a hole for the yard boundary with the house cut out of the middle, shown here as the thick solid line.

Listing 6.4. Creating a polygon with a hole

Likely, creating a donut was easier than you expected. Now if you take a look at the WKT, you’ll see two rings shown inside the polygon:

>>> vp.plot(yard, 'yellow')
>>> print(yard)
POLYGON ((58.0 38.5 0,53 6 0,99.5 19.0 0,73 42 0,58.0 38.5 0),
 (67.5 29.0 0,69.0 25.5 0,64 23 0,69 15 0,82.5 22.0 0,76.0 31.5 0,
 67.5 29.0 0))

The holes are taken into account when using the polygon. For example, the area of the yard polygon is equal to the area of the lot with the house subtracted out. The hole in the polygon isn’t considered part of the geometry when using the spatial analysis tools shown in the next section, either.

The only difference when editing a polygon like this is that you need to loop through each of the rings instead of assuming only one exists (figure 6.22). In practice, you shouldn’t ever assume only one ring exists, anyway, because that assumption could come back to haunt you later.

Figure 6.22. The original and edited yard polygon geometries with the house cut out. The original is filled and the edited one is hatched.

>>> for i in range(yard.GetGeometryCount()):
...     ring = yard.GetGeometryRef(i)
...     for j in range(ring.GetPointCount()):
...         ring.SetPoint(j, ring.GetX(j) - 5, ring.GetY(j))
...
>>> vp.plot(yard, fill=False, hatch='x', color='blue')

Using other modules to work with geometries

Now that you understand how to deal with geometries using OGR, other geometry libraries, such as Fiona, should be easy to understand. Lines are still created by an ordered collection of vertices, and polygons are still made up of rings. The underlying theory about geometries doesn’t change, although the method of accessing them does.

Fiona, for example, is a library for reading and writing vector data that’s built on top of OGR. Fiona doesn’t use special geometry types, but instead uses Python lists to store vertices. The lists are filled with tuples that contain the vertex coordinates. For example, a ring is a list of tuples, and a polygon is a list of rings. A polygon with one ring is a list that contains another list that contains tuples for the vertices. The Fiona user manual is excellent and can be found online at http://toblerity.org/fiona/manual.html.

Shapely is another outstanding module that’s designed for working with geometries, but not reading and writing data. Unlike Fiona, it does have special data types for geometries, but that’s why it can do spatial analysis, unlike Fiona. Even though it has its own data types, the general ideas are still the same. The detailed user manual for Shapely is available online at http://toblerity.org/shapely/manual.html.

6.5. Summary

  • A geometry consists of a collection of vertices. In the cases of lines and polygons, the vertices are connected by line segments to form shapes.
  • Multigeometries consist of multiple geometries combined into one. This allows features such as Hawaii to be represented with a single geometry object.
  • Geometries in OGR are either 2D or 2.5D. The 2.5D geometries have z values, but they’re ignored during analyses, which is why they aren’t considered 3D.
  • All polygon geometries are made up of one or more rings.
..................Content has been hidden....................

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