├── ospybook ├── MANIFEST.in ├── README.txt ├── MANIFEST ├── CHANGES.txt ├── setup.py ├── LICENSE.txt └── ospybook │ ├── vectorplotter.py │ └── __init__.py ├── Chapter10 ├── listing10_5.py ├── ch10funcs.py ├── listing10_3.py ├── listing10_5.txt ├── listing10_4.py ├── listing10_1.py ├── listing10_2.py ├── listing10_6.py ├── listing10_7.py └── chapter10.py ├── ospybook-latest.zip ├── Chapter7 ├── listing7_3.py ├── ch7funcs.py ├── listing7_10.py ├── listing7_2.py ├── listing7_8.py ├── listing7_1.py ├── listing7_5.py ├── listing7_7.py ├── listing7_6.py ├── listing7_4.py ├── listing7_9.py └── chapter7.py ├── Chapter2 ├── myfuncs.py └── chapter2.py ├── Chapter13 ├── ch13funcs.py ├── listing13_6.py ├── listing13_1.py ├── listing13_5.py ├── listing13_13.py ├── listing13_12.xml ├── listing13_8.py ├── listing13_11.py ├── listing13_9-edited.py ├── listing13_14.py ├── listing13_2.py ├── listing13_7.py ├── listing13_4.py ├── listing13_9.py ├── listing13_3.py ├── listing13_10.py └── chapter13.py ├── Chapter3 ├── listing3_2.txt ├── listing3_1.py └── listing3_3.py ├── Chapter12 ├── listing12_1.py ├── listing12_2.py ├── listing12_4.py └── listing12_3.py ├── Chapter11 ├── listing11_6.py ├── listing11_11.py ├── listing11_13.py ├── listing11_12.py ├── listing11_3.py ├── listing11_14.py ├── listing11_2.py ├── listing11_4.py ├── listing11_1.py ├── listing11_5.py ├── listing11_9.py ├── listing11_7.py ├── listing11_8.py ├── listing11_10.py └── chapter11.py ├── Chapter4 ├── listing4_1.geojson ├── listing4_2.py ├── listing4_4.py ├── listing4_3.py └── chapter4.py ├── Chapter9 ├── listing9_6.xml ├── listing9_4.py ├── listing9_1.py ├── listing9_5.py ├── listing9_2.py ├── listing9_3.py └── chapter9.py ├── .gitignore ├── Chapter6 ├── listing6_3.py ├── listing6_4.py ├── listing6_1.py ├── listing6_2.py └── chapter6.py ├── README.md ├── LICENSE ├── Chapter8 ├── listing8_1.py └── chapter8.py └── Chapter5 └── chapter5.py /ospybook/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | recursive-include docs *.txt 3 | -------------------------------------------------------------------------------- /Chapter10/listing10_5.py: -------------------------------------------------------------------------------- 1 | See /osgeopy-data/Landsat/Washington/simple_example.vrt 2 | -------------------------------------------------------------------------------- /ospybook-latest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgarrard/osgeopy-code/HEAD/ospybook-latest.zip -------------------------------------------------------------------------------- /Chapter7/listing7_3.py: -------------------------------------------------------------------------------- 1 | # See line 194 of chapter7.py, since the listing needs previous code in order to run. 2 | -------------------------------------------------------------------------------- /ospybook/README.txt: -------------------------------------------------------------------------------- 1 | ospybook 2 | ======== 3 | 4 | This module was written as a companion to `Geoprocessing with Python `_. 5 | -------------------------------------------------------------------------------- /ospybook/MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | CHANGES.txt 3 | LICENSE.txt 4 | README.txt 5 | setup.py 6 | ospybook\__init__.py 7 | ospybook\simplevectorplotter.py 8 | ospybook\vectorplotter.py 9 | -------------------------------------------------------------------------------- /ospybook/CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.1, 8-3-2014 -- Initial release. 2 | v0.11, 9-14-2014 -- Added copy_datasource and print_capabilities functions. 3 | v0.12, 10-24-2014 -- Bug fixes. 4 | v0.13, 10-15-2015 -- Raster tools. 5 | v1.0 3-27-16 -- Updated vector plotter for new version of Matplotlib. 6 | -------------------------------------------------------------------------------- /Chapter10/ch10funcs.py: -------------------------------------------------------------------------------- 1 | from osgeo import gdal 2 | 3 | def get_extent(fn): 4 | '''Returns min_x, max_y, max_x, min_y''' 5 | ds = gdal.Open(fn) 6 | gt = ds.GetGeoTransform() 7 | return (gt[0], gt[3], gt[0] + gt[1] * ds.RasterXSize, 8 | gt[3] + gt[5] * ds.RasterYSize) 9 | -------------------------------------------------------------------------------- /Chapter2/myfuncs.py: -------------------------------------------------------------------------------- 1 | def factorial(n, print_it=False): 2 | """Retuns the factorial of n, optionally printing it first.""" 3 | answer = 1 4 | for i in range(1, n + 1): 5 | answer = answer * i 6 | if print_it: 7 | print('{0}! = {1}'.format(n, answer)) 8 | return answer 9 | -------------------------------------------------------------------------------- /Chapter13/ch13funcs.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from osgeo import ogr 3 | 4 | def plot_polygon(poly, symbol='k-', **kwargs): 5 | """Plots a polygon using the given symbol.""" 6 | for i in range(poly.GetGeometryCount()): 7 | subgeom = poly.GetGeometryRef(i) 8 | x, y = zip(*subgeom.GetPoints()) 9 | plt.plot(x, y, symbol, **kwargs) 10 | -------------------------------------------------------------------------------- /Chapter7/ch7funcs.py: -------------------------------------------------------------------------------- 1 | # Function to get unique values from an attribute field. 2 | def get_unique(datasource, layer_name, field_name): 3 | sql = 'SELECT DISTINCT {0} FROM {1}'.format(field_name, layer_name) 4 | lyr = datasource.ExecuteSQL(sql) 5 | values = [] 6 | for row in lyr: 7 | values.append(row.GetField(field_name)) 8 | datasource.ReleaseResultSet(lyr) 9 | return values 10 | -------------------------------------------------------------------------------- /Chapter3/listing3_2.txt: -------------------------------------------------------------------------------- 1 | GEOGCS["NAD83", 2 | DATUM["North_American_Datum_1983", 3 | SPHEROID["GRS 1980",6378137,298.257222101, 4 | AUTHORITY["EPSG","7019"]], 5 | TOWGS84[0,0,0,0,0,0,0], 6 | AUTHORITY["EPSG","6269"]], 7 | PRIMEM["Greenwich",0, 8 | AUTHORITY["EPSG","8901"]], 9 | UNIT["degree",0.0174532925199433, 10 | AUTHORITY["EPSG","9122"]], 11 | AUTHORITY["EPSG","4269"]] 12 | -------------------------------------------------------------------------------- /Chapter12/listing12_1.py: -------------------------------------------------------------------------------- 1 | # Function to stack raster bands. 2 | 3 | import numpy as np 4 | from osgeo import gdal 5 | 6 | def stack_bands(filenames): 7 | """Returns a 3D array containing all band data from all files.""" 8 | bands = [] 9 | for fn in filenames: 10 | ds = gdal.Open(fn) 11 | for i in range(1, ds.RasterCount + 1): 12 | bands.append(ds.GetRasterBand(i).ReadAsArray()) 13 | return np.dstack(bands) 14 | -------------------------------------------------------------------------------- /ospybook/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='ospybook', 5 | version='1.0', 6 | author='Chris Garrard', 7 | author_email='garrard.chris@gmail.com', 8 | packages=['ospybook'], 9 | url='https://github.com/cgarrard/osgeopy-code/', 10 | license='LICENSE.txt', 11 | description='Module for the book Geoprocessing with Python.', 12 | long_description=open('README.txt').read(), 13 | install_requires=[ 14 | "numpy", 15 | "matplotlib", 16 | # "osgeo", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /Chapter11/listing11_6.py: -------------------------------------------------------------------------------- 1 | # Script that uses SciPy to smooth a DEM. 2 | 3 | import os 4 | import scipy.ndimage 5 | from osgeo import gdal 6 | import ospybook as pb 7 | 8 | in_fn = r"D:\osgeopy-data\Nepal\everest.tif" 9 | out_fn = r'D:\Temp\everest_smoothed.tif' 10 | 11 | in_ds = gdal.Open(in_fn) 12 | in_data = in_ds.GetRasterBand(1).ReadAsArray() 13 | 14 | # Use SciPy to run a 3x3 filter. 15 | out_data = scipy.ndimage.filters.uniform_filter( 16 | in_data, size=3, mode='nearest') 17 | 18 | pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Int32) 19 | del in_ds 20 | -------------------------------------------------------------------------------- /Chapter4/listing4_1.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { "NAME": "Geneva", "PLACE": "city" }, 7 | "geometry": { 8 | "type": "Point", 9 | "coordinates": [ 6.1465886, 46.2017589 ] 10 | } 11 | }, 12 | { 13 | "type": "Feature", 14 | "properties": { "NAME": "Lausanne", "PLACE": "city" }, 15 | "geometry": { 16 | "type": "Point", 17 | "coordinates": [ 6.6327025, 46.5218269 ] 18 | } 19 | }, 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /Chapter11/listing11_11.py: -------------------------------------------------------------------------------- 1 | def make_resample_slices(data, win_size): 2 | """Return a list of resampled slices given a window size. 3 | 4 | data - two-dimensional array to get slices from 5 | win_size - tuple of (rows, columns) for the input window 6 | """ 7 | row = int(data.shape[0] / win_size[0]) * win_size[0] 8 | col = int(data.shape[1] / win_size[1]) * win_size[1] 9 | slices = [] 10 | 11 | for i in range(win_size[0]): 12 | for j in range(win_size[1]): 13 | slices.append(data[i:row:win_size[0], j:col:win_size[1]]) 14 | return slices 15 | -------------------------------------------------------------------------------- /Chapter7/listing7_10.py: -------------------------------------------------------------------------------- 1 | from osgeo import ogr 2 | 3 | ds = ogr.Open(r'D:\osgeopy-data\Galapagos') 4 | lyr = ds.GetLayerByName('albatross_ranges2') 5 | lyr.SetAttributeFilter("tag_id = '1163-1163' and location = 'island'") 6 | row = next(lyr) 7 | all_areas = row.geometry().Clone() 8 | common_areas = row.geometry().Clone() 9 | for row in lyr: 10 | all_areas = all_areas.Union(row.geometry()) 11 | common_areas = common_areas.Intersection(row.geometry()) 12 | percent = common_areas.GetArea() / all_areas.GetArea() * 100 13 | print('Percent of all area used in every visit: {0}'. format(percent)) 14 | -------------------------------------------------------------------------------- /Chapter13/listing13_6.py: -------------------------------------------------------------------------------- 1 | # Function to stretch and scale data. 2 | 3 | import numpy as np 4 | 5 | def stretch_data(data, num_stddev): 6 | """Returns the data with a standard deviation stretch applied. 7 | 8 | data - array containing data to stretch 9 | num_stddev - number of standard deviations to use 10 | """ 11 | mean = np.mean(data) 12 | std_range = np.std(data) * 2 13 | new_min = max(mean - std_range, np.min(data)) 14 | new_max = min(mean + std_range, np.max(data)) 15 | clipped_data = np.clip(data, new_min, new_max) 16 | return clipped_data / (new_max - new_min) 17 | -------------------------------------------------------------------------------- /Chapter13/listing13_1.py: -------------------------------------------------------------------------------- 1 | # Plot world landmasses as simple polygons. 2 | 3 | import matplotlib.pyplot as plt 4 | from osgeo import ogr 5 | 6 | ds = ogr.Open(r'D:\osgeopy-data\global\ne_110m_land.shp') 7 | lyr = ds.GetLayer(0) 8 | for row in lyr: 9 | geom = row.geometry() 10 | ring = geom.GetGeometryRef(0) 11 | 12 | # This returns a list of (x,y) tuples. 13 | coords = ring.GetPoints() 14 | x, y = zip(*coords) 15 | 16 | # The 'k' means black. Try 'b' or 'r'. 17 | plt.plot(x, y, 'k') 18 | 19 | # Equalize the axis units so things aren't warped. Comment out 20 | # this line and see what happens. 21 | plt.axis('equal') 22 | plt.show() 23 | -------------------------------------------------------------------------------- /Chapter9/listing9_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.3.0 4 | http://raster.nationalmap.gov/arcgis/services/Orthoimagery/USGS_EROS_Ortho_1Foot/ImageServer/WMSServer? 5 | CRS:84 6 | image/png 7 | 0 8 | 9 | 10 | -74.054444 11 | 40.699167 12 | -74.034444 13 | 40.679167 14 | 300 15 | 300 16 | 17 | 4 18 | 19 | -------------------------------------------------------------------------------- /Chapter11/listing11_13.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def bilinear(in_data, x, y): 4 | """Performs bilinear interpolation. 5 | 6 | in_data - the input dataset to be resampled 7 | x - an array of x coordinates for output pixel centers 8 | y - an array of y coordinates for output pixel centers 9 | """ 10 | x -= 0.5 11 | y -= 0.5 12 | x0 = np.floor(x).astype(int) 13 | x1 = x0 + 1 14 | y0 = np.floor(y).astype(int) 15 | y1 = y0 + 1 16 | 17 | ul = in_data[y0, x0] * (y1 - y) * (x1 - x) 18 | ur = in_data[y0, x1] * (y1 - y) * (x - x0) 19 | ll = in_data[y1, x0] * (y - y0) * (x1 - x) 20 | lr = in_data[y1, x1] * (y - y0) * (x - x0) 21 | 22 | return ul + ur + ll + lr 23 | -------------------------------------------------------------------------------- /Chapter3/listing3_1.py: -------------------------------------------------------------------------------- 1 | # Script to read attributes from a shapefile. 2 | 3 | # Don't forget to import ogr 4 | import sys 5 | from osgeo import ogr 6 | 7 | # Open the data source and get the layer 8 | fn = r'D:\osgeopy-data\global\ne_50m_populated_places.shp' 9 | ds = ogr.Open(fn, 0) 10 | if ds is None: 11 | sys.exit('Could not open {0}.'.format(fn)) 12 | lyr = ds.GetLayer(0) 13 | 14 | i = 0 15 | for feat in lyr: 16 | 17 | # Get the x,y coordinates 18 | pt = feat.geometry() 19 | x = pt.GetX() 20 | y = pt.GetY() 21 | 22 | # Get the attribute values 23 | name = feat.GetField('NAME') 24 | pop = feat.GetField('POP_MAX') 25 | print(name, pop, x, y) 26 | i += 1 27 | if i == 10: 28 | break 29 | del ds 30 | -------------------------------------------------------------------------------- /Chapter11/listing11_12.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def get_indices(source_ds, target_width, target_height): 4 | """Returns x, y lists of all possible resampling offsets. 5 | 6 | source_ds - dataset to get offsets from 7 | target_width - target pixel width 8 | target_height - target pixel height (negative) 9 | """ 10 | source_geotransform = source_ds.GetGeoTransform() 11 | source_width = source_geotransform[1] 12 | source_height = source_geotransform[5] 13 | dx = target_width / source_width 14 | dy = target_height / source_height 15 | target_x = np.arange(dx / 2, source_ds.RasterXSize, dx) 16 | target_y = np.arange(dy / 2, source_ds.RasterYSize, dy) 17 | return np.meshgrid(target_x, target_y) 18 | -------------------------------------------------------------------------------- /Chapter13/listing13_5.py: -------------------------------------------------------------------------------- 1 | # Function to retrieve overview data from a raster. 2 | 3 | import gdal 4 | 5 | def get_overview_data(fn, band_index=1, level=-1): 6 | """Returns an array containing data from an overview. 7 | 8 | fn - path to raster file 9 | band_index - band number to get overview for 10 | level - overview level, where 1 is the highest resolution; 11 | the coarsest can be retrieved with -1 12 | """ 13 | ds = gdal.Open(fn) 14 | band = ds.GetRasterBand(band_index) 15 | if level > 0: 16 | ov_band = band.GetOverview(level) 17 | else: 18 | num_ov = band.GetOverviewCount() 19 | ov_band = band.GetOverview(num_ov + level) 20 | return ov_band.ReadAsArray() 21 | -------------------------------------------------------------------------------- /Chapter11/listing11_3.py: -------------------------------------------------------------------------------- 1 | # Function to get slices of any size from an array. 2 | 3 | import numpy as np 4 | 5 | def make_slices(data, win_size): 6 | """Return a list of slices given a window size. 7 | 8 | data - two-dimensional array to get slices from 9 | win_size - tuple of (rows, columns) for the moving window 10 | """ 11 | # Calculate the slice size 12 | rows = data.shape[0] - win_size[0] + 1 13 | cols = data.shape[1] - win_size[1] + 1 14 | slices = [] 15 | 16 | # Loop through the rows and columns in the provided window size and 17 | # create each slice. 18 | for i in range(win_size[0]): 19 | for j in range(win_size[1]): 20 | slices.append(data[i:rows+i, j:cols+j]) 21 | return slices 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ignore/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | bin/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # Installer logs 28 | pip-log.txt 29 | pip-delete-this-directory.txt 30 | 31 | # Unit test / coverage reports 32 | htmlcov/ 33 | .tox/ 34 | .coverage 35 | .cache 36 | nosetests.xml 37 | coverage.xml 38 | 39 | # Translations 40 | *.mo 41 | 42 | # Mr Developer 43 | .mr.developer.cfg 44 | .project 45 | .pydevproject 46 | 47 | # Rope 48 | .ropeproject 49 | 50 | # Django stuff: 51 | *.log 52 | *.pot 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | -------------------------------------------------------------------------------- /Chapter6/listing6_3.py: -------------------------------------------------------------------------------- 1 | # Make the garden boxes multipolygon. Create a regular polygon for each raised 2 | # bed, and then add it to the multipolygon. 3 | 4 | # This code is also in chapter6.py so that you can use it with the other examples. 5 | 6 | box1 = ogr.Geometry(ogr.wkbLinearRing) 7 | box1.AddPoint(87.5, 25.5) 8 | box1.AddPoint(89, 25.5) 9 | box1.AddPoint(89, 24) 10 | box1.AddPoint(87.5, 24) 11 | garden1 = ogr.Geometry(ogr.wkbPolygon) 12 | garden1.AddGeometry(box1) 13 | 14 | box2 = ogr.Geometry(ogr.wkbLinearRing) 15 | box2.AddPoint(89, 23) 16 | box2.AddPoint(92, 23) 17 | box2.AddPoint(92,22) 18 | box2.AddPoint(89,22) 19 | garden2 = ogr.Geometry(ogr.wkbPolygon) 20 | garden2.AddGeometry(box2) 21 | 22 | gardens = ogr.Geometry(ogr.wkbMultiPolygon) 23 | gardens.AddGeometry(garden1) 24 | gardens.AddGeometry(garden2) 25 | gardens.CloseRings() 26 | -------------------------------------------------------------------------------- /Chapter13/listing13_13.py: -------------------------------------------------------------------------------- 1 | # Script to draw a topo map with mapnik. 2 | 3 | import mapnik 4 | srs = '+proj=utm +zone=10 +ellps=GRS80 +datum=NAD83 +units=m +no_defs' 5 | m = mapnik.Map(1200, 1200, srs) 6 | m.zoom_to_box(mapnik.Box2d(558800, 5112200, 566600, 5120500)) 7 | 8 | # Add a GDAL datasource. 9 | topo_lyr = mapnik.Layer('Topo', srs) 10 | topo_raster = mapnik.Gdal( 11 | file=r'D:\osgeopy-data\Washington\dem\st_helens.tif') 12 | topo_lyr.datasource = topo_raster 13 | 14 | # Use a raster symbolizer. 15 | topo_sym = mapnik.RasterSymbolizer() 16 | topo_rule = mapnik.Rule() 17 | topo_rule.symbols.append(topo_sym) 18 | topo_style = mapnik.Style() 19 | topo_style.rules.append(topo_rule) 20 | 21 | m.append_style('topo', topo_style) 22 | topo_lyr.styles.append('topo') 23 | 24 | m.layers.append(topo_lyr) 25 | mapnik.render_to_file(m, r'd:\temp\helens.png') 26 | -------------------------------------------------------------------------------- /Chapter7/listing7_2.py: -------------------------------------------------------------------------------- 1 | # Use a better method to count the cities within 16,000 meters of a volcano, 2 | # because it doesn't double-count stuff. 3 | 4 | import ogr 5 | 6 | # Open the layers as in listing 7.1 instead of reset reading. 7 | shp_ds = ogr.Open(r'D:\osgeopy-data\US') 8 | volcano_lyr = shp_ds.GetLayer('us_volcanos_albers') 9 | cities_lyr = shp_ds.GetLayer('cities_albers') 10 | 11 | # This time, add each buffer to a multipolygon instead of a temporary later. 12 | multipoly = ogr.Geometry(ogr.wkbMultiPolygon) 13 | for volcano_feat in volcano_lyr: 14 | buff_geom = volcano_feat.geometry().Buffer(16000) 15 | multipoly.AddGeometry(buff_geom) 16 | 17 | # Then union all of the buffers together to get one polygon which you can use 18 | # as a spatial filter. 19 | cities_lyr.SetSpatialFilter(multipoly.UnionCascaded()) 20 | print('Cities: {}'.format(cities_lyr.GetFeatureCount())) 21 | -------------------------------------------------------------------------------- /Chapter12/listing12_2.py: -------------------------------------------------------------------------------- 1 | # Script to perform K-means clustering with Spectral Python. 2 | 3 | import os 4 | import numpy as np 5 | import spectral 6 | from osgeo import gdal 7 | import ospybook as pb 8 | 9 | folder = r'D:\osgeopy-data\Landsat\Utah' 10 | raster_fns = ['LE70380322000181EDC02_60m.tif', 11 | 'LE70380322000181EDC02_TIR_60m.tif'] 12 | out_fn = 'kmeans_prediction_60m.tif' 13 | 14 | # Stack the bands and run the analysis. 15 | os.chdir(folder) 16 | data = pb.stack_bands(raster_fns) 17 | classes, centers = spectral.kmeans(data) 18 | 19 | # Save the output. 20 | ds = gdal.Open(raster_fns[0]) 21 | out_ds = pb.make_raster(ds, out_fn, classes, gdal.GDT_Byte) 22 | levels = pb.compute_overview_levels(out_ds.GetRasterBand(1)) 23 | out_ds.BuildOverviews('NEAREST', levels) 24 | out_ds.FlushCache() 25 | out_ds.GetRasterBand(1).ComputeStatistics(False) 26 | 27 | del out_ds, ds 28 | -------------------------------------------------------------------------------- /Chapter11/listing11_14.py: -------------------------------------------------------------------------------- 1 | # Script to run a bilinear interpolation 2 | 3 | from osgeo import gdal 4 | import listing11_12 5 | import listing11_13 6 | 7 | in_fn = r"D:\osgeopy-data\Nepal\everest.tif" 8 | out_fn = r'D:\Temp\everest_bilinear.tif' 9 | cell_size = (0.02, -0.02) 10 | 11 | in_ds = gdal.Open(in_fn) 12 | x, y = listing11_12.get_indices(in_ds, *cell_size) 13 | outdata = listing11_13.bilinear(in_ds.ReadAsArray(), x, y) 14 | 15 | driver = gdal.GetDriverByName('GTiff') 16 | rows, cols = outdata.shape 17 | out_ds = driver.Create( 18 | out_fn, cols, rows, 1, gdal.GDT_Int32) 19 | out_ds.SetProjection(in_ds.GetProjection()) 20 | 21 | gt = list(in_ds.GetGeoTransform()) 22 | gt[1] = cell_size[0] 23 | gt[5] = cell_size[1] 24 | out_ds.SetGeoTransform(gt) 25 | 26 | out_band = out_ds.GetRasterBand(1) 27 | out_band.WriteArray(outdata) 28 | out_band.FlushCache() 29 | out_band.ComputeStatistics(False) 30 | -------------------------------------------------------------------------------- /Chapter11/listing11_2.py: -------------------------------------------------------------------------------- 1 | # Script to compute NDVI. 2 | 3 | import os 4 | import numpy as np 5 | from osgeo import gdal 6 | import ospybook as pb 7 | 8 | os.chdir(r'D:\osgeopy-data\Massachusetts') 9 | in_fn = 'm_4207162_ne_19_1_20140718_20140923_clip.tif' 10 | out_fn = 'ndvi.tif' 11 | 12 | ds = gdal.Open(in_fn) 13 | red = ds.GetRasterBand(1).ReadAsArray().astype(np.float) 14 | nir = ds.GetRasterBand(4).ReadAsArray() 15 | 16 | # Mask the red band. 17 | red = np.ma.masked_where(nir + red == 0, red) 18 | 19 | # Do the calculation. 20 | ndvi = (nir - red) / (nir + red) 21 | 22 | # Fill the empty cells. 23 | ndvi = ndvi.filled(-99) 24 | 25 | # Set NoData to the fill value when creating the new raster. 26 | out_ds = pb.make_raster( 27 | ds, out_fn, ndvi, gdal.GDT_Float32, -99) 28 | 29 | overviews = pb.compute_overview_levels(out_ds.GetRasterBand(1)) 30 | out_ds.BuildOverviews('average', overviews) 31 | del ds, out_ds 32 | -------------------------------------------------------------------------------- /Chapter11/listing11_4.py: -------------------------------------------------------------------------------- 1 | # Script to smooth an elevation dataset. 2 | 3 | import os 4 | import numpy as np 5 | from osgeo import gdal 6 | import ospybook as pb 7 | 8 | in_fn = r"D:\osgeopy-data\Nepal\everest.tif" 9 | out_fn = r'D:\Temp\everest_smoothed_edges.tif' 10 | 11 | in_ds = gdal.Open(in_fn) 12 | in_band = in_ds.GetRasterBand(1) 13 | in_data = in_band.ReadAsArray() 14 | 15 | # Stack the slices 16 | slices = pb.make_slices(in_data, (3, 3)) 17 | stacked_data = np.ma.dstack(slices) 18 | 19 | rows, cols = in_band.YSize, in_band.XSize 20 | 21 | # Initialize an output array to the NoData value (-99) 22 | out_data = np.ones((rows, cols), np.int32) * -99 23 | 24 | # Put the result into the middle of the output, leaving the 25 | # outside rows and columns alone, so they still have -99. 26 | out_data[1:-1, 1:-1] = np.mean(stacked_data, 2) 27 | 28 | pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Int32, -99) 29 | del in_ds 30 | -------------------------------------------------------------------------------- /Chapter10/listing10_3.py: -------------------------------------------------------------------------------- 1 | # Script to add a color table to a raster. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Don't forget to change directory. 7 | os.chdir(r'D:\osgeopy-data\Switzerland') 8 | 9 | # Create a copy of the raster so you still have the original unmodified. 10 | original_ds = gdal.Open('dem_class.tif') 11 | driver = gdal.GetDriverByName('gtiff') 12 | ds = driver.CreateCopy('dem_class2.tif', original_ds) 13 | band = ds.GetRasterBand(1) 14 | 15 | # Create a color table with colors for pixel values 1-5. 16 | colors = gdal.ColorTable() 17 | colors.SetColorEntry(1, (112, 153, 89)) 18 | colors.SetColorEntry(2, (242, 238, 162)) 19 | colors.SetColorEntry(3, (242, 206, 133)) 20 | colors.SetColorEntry(4, (194, 140, 124)) 21 | colors.SetColorEntry(5, (214, 193, 156)) 22 | 23 | # Add the color table to the raster. 24 | band.SetRasterColorTable(colors) 25 | band.SetRasterColorInterpretation( 26 | gdal.GCI_PaletteIndex) 27 | 28 | del band, ds 29 | 30 | -------------------------------------------------------------------------------- /Chapter6/listing6_4.py: -------------------------------------------------------------------------------- 1 | # Make the yard multipolygon, but this time cut out a hole for the house. 2 | # Make the outer boundary ring first (it's the same as earlier). Then make the 3 | # ring for the house (hole). Then create a polygon and add the rings, making 4 | # sure to add the outer boundary first. Close all of the rings at once by 5 | # calling CloseRings *after* adding all of the rings. 6 | 7 | # This code is also in chapter6.py so that you can use it with the other examples. 8 | 9 | lot = ogr.Geometry(ogr.wkbLinearRing) 10 | lot.AddPoint(58, 38.5) 11 | lot.AddPoint(53, 6) 12 | lot.AddPoint(99.5, 19) 13 | lot.AddPoint(73, 42) 14 | 15 | house = ogr.Geometry(ogr.wkbLinearRing) 16 | house.AddPoint(67.5, 29) 17 | house.AddPoint(69, 25.5) 18 | house.AddPoint(64, 23) 19 | house.AddPoint(69, 15) 20 | house.AddPoint(82.5, 22) 21 | house.AddPoint(76, 31.5) 22 | 23 | yard = ogr.Geometry(ogr.wkbPolygon) 24 | yard.AddGeometry(lot) 25 | yard.AddGeometry(house) 26 | yard.CloseRings() 27 | -------------------------------------------------------------------------------- /Chapter13/listing13_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 18 | atlas 19 | 20 | D:\osgeopy-data\US\wtrbdyp010 21 | shape 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter11/listing11_1.py: -------------------------------------------------------------------------------- 1 | # Function to create a new 1-band raster and use projection 2 | # and geotransform from another raster. 3 | 4 | from osgeo import gdal 5 | 6 | def make_raster(in_ds, fn, data, data_type, nodata=None): 7 | """Create a one-band GeoTIFF. 8 | 9 | in_ds - datasource to copy projection and geotransform from 10 | fn - path to the file to create 11 | data - NumPy array containing data to write 12 | data_type - output data type 13 | nodata - optional NoData value 14 | """ 15 | driver = gdal.GetDriverByName('GTiff') 16 | out_ds = driver.Create( 17 | fn, in_ds.RasterXSize, in_ds.RasterYSize, 1, data_type) 18 | out_ds.SetProjection(in_ds.GetProjection()) 19 | out_ds.SetGeoTransform(in_ds.GetGeoTransform()) 20 | out_band = out_ds.GetRasterBand(1) 21 | if nodata is not None: 22 | out_band.SetNoDataValue(nodata) 23 | out_band.WriteArray(data) 24 | out_band.FlushCache() 25 | out_band.ComputeStatistics(False) 26 | return out_ds 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osgeopy-code 2 | 3 | Code for the book [*Geoprocessing with Python*](http://manning.com/garrard/?a_aid=geopy&a_bid=c3bae5be). 4 | 5 | Corresponding data files can be found at (it used to be on github, but the raster files were well past github's file size limit). 6 | 7 | The source code for the ospybook module referenced in the text is in the ospybook folder. You can install it like this: 8 | 9 | ```bash 10 | pip install [path_to_osgeopy-code]/ospybook-latest.zip 11 | ``` 12 | 13 | Or directly from GitHub like this: 14 | 15 | ```bash 16 | pip install https://github.com/cgarrard/osgeopy-code/raw/master/ospybook-latest.zip 17 | ``` 18 | 19 | Note that these commands are not run from a Python session. Instead, run them from a terminal window. If pip is not in your path, just provide the full path to it. For example, I could run it from a command prompt on my Windows machine like this: 20 | 21 | ```text 22 | C:\Python33\Scripts\pip install https://github.com/cgarrard/osgeopy-code/raw/master/ospybook-latest.zip 23 | ``` 24 | -------------------------------------------------------------------------------- /Chapter13/listing13_8.py: -------------------------------------------------------------------------------- 1 | # Script to create a simple mapnik map. 2 | 3 | import mapnik 4 | 5 | # Create the map object. 6 | srs = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs" 7 | m = mapnik.Map(800, 600, srs) 8 | m.zoom_to_box(mapnik.Box2d(-90.3, 29.7, -89.5, 30.3)) 9 | 10 | # Create a layer from a shapefile. 11 | tiger_fn = r'D:\osgeopy-data\Louisiana\tiger_la_water_CENSUS_2006' 12 | tiger_shp = mapnik.Shapefile(file=tiger_fn) 13 | tiger_lyr = mapnik.Layer('Tiger') 14 | tiger_lyr.datasource = tiger_shp 15 | 16 | # Create a polygon fill symbol. 17 | water_color = mapnik.Color(165, 191, 221) 18 | water_fill_sym = mapnik.PolygonSymbolizer(water_color) 19 | 20 | # Create a symbology style and add it to the layer. 21 | tiger_rule = mapnik.Rule() 22 | tiger_rule.symbols.append(water_fill_sym) 23 | tiger_style = mapnik.Style() 24 | tiger_style.rules.append(tiger_rule) 25 | m.append_style('tiger', tiger_style) 26 | 27 | # Add the style and layer to the map. 28 | tiger_lyr.styles.append('tiger') 29 | m.layers.append(tiger_lyr) 30 | 31 | # Save the map. 32 | mapnik.render_to_file(m, r'd:\temp\nola.png') 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Garrard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Chapter13/listing13_11.py: -------------------------------------------------------------------------------- 1 | # Script to create an xml file to describe the National Atlas 2 | # hydrography layer. 3 | 4 | import mapnik 5 | 6 | m = mapnik.Map(0, 0) 7 | 8 | water_rule = mapnik.Rule() 9 | water_rule.filter = mapnik.Expression( 10 | "[Feature]='Canal' or [Feature]='Lake'") 11 | water_rule.symbols.append( 12 | mapnik.PolygonSymbolizer(mapnik.Color(165, 191, 221))) 13 | 14 | marsh_rule = mapnik.Rule() 15 | marsh_rule.filter = mapnik.Expression("[Feature]='Swamp or Marsh'") 16 | marsh_color = mapnik.Color('#66AA66') 17 | marsh_rule.symbols.append(mapnik.PolygonSymbolizer(marsh_color)) 18 | marsh_rule.symbols.append(mapnik.LineSymbolizer(marsh_color, 2)) 19 | 20 | atlas_style = mapnik.Style() 21 | atlas_style.rules.append(water_rule) 22 | atlas_style.rules.append(marsh_rule) 23 | m.append_style('atlas', atlas_style) 24 | 25 | lyr = mapnik.Layer('National Atlas Hydro', 26 | "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs") 27 | lyr.datasource = mapnik.Shapefile(file=r'D:\osgeopy-data\US\wtrbdyp010') 28 | lyr.styles.append('atlas') 29 | m.layers.append(lyr) 30 | 31 | mapnik.save_map(m, r'D:\osgeopy-data\US\national_atlas_hydro.xml') 32 | -------------------------------------------------------------------------------- /ospybook/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Garrard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter13/listing13_9-edited.py: -------------------------------------------------------------------------------- 1 | # Script demonstrating how to xml in a mapnik map. 2 | 3 | import mapnik 4 | 5 | # Create the map object. 6 | srs = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs" 7 | m = mapnik.Map(800, 600, srs) 8 | m.zoom_to_box(mapnik.Box2d(-90.3, 29.7, -89.5, 30.3)) 9 | 10 | # Create a layer from a shapefile. 11 | tiger_fn = r'D:\osgeopy-data\Louisiana\tiger_la_water_CENSUS_2006' 12 | tiger_shp = mapnik.Shapefile(file=tiger_fn) 13 | tiger_lyr = mapnik.Layer('Tiger') 14 | tiger_lyr.datasource = tiger_shp 15 | 16 | # Create a polygon fill symbol. 17 | water_color = mapnik.Color(165, 191, 221) 18 | water_fill_sym = mapnik.PolygonSymbolizer(water_color) 19 | 20 | # Create a symbology style and add it to the layer. 21 | tiger_rule = mapnik.Rule() 22 | tiger_rule.symbols.append(water_fill_sym) 23 | tiger_style = mapnik.Style() 24 | tiger_style.rules.append(tiger_rule) 25 | m.append_style('tiger', tiger_style) 26 | 27 | # All of the code for the atlas layer is now gone... 28 | 29 | # Add the atlas layer using the xml. 30 | mapnik.load_map(m, r'D:\osgeopy-data\US\national_atlas_hydro.xml') 31 | m.layers.append(tiger_lyr) 32 | 33 | # Save the map. 34 | mapnik.render_to_file(m, r'd:\temp\nola5.png') 35 | -------------------------------------------------------------------------------- /Chapter4/listing4_2.py: -------------------------------------------------------------------------------- 1 | import os 2 | from osgeo import ogr 3 | 4 | def layers_to_feature_dataset(ds_name, gdb_fn, dataset_name): 5 | """Copy layers to a feature dataset in a file geodatabase.""" 6 | 7 | # Open the input datasource. 8 | in_ds = ogr.Open(ds_name) 9 | if in_ds is None: 10 | raise RuntimeError('Could not open datasource') 11 | 12 | # Open the geodatabase or create it if it doesn't exist. 13 | gdb_driver = ogr.GetDriverByName('FileGDB') 14 | if os.path.exists(gdb_fn): 15 | gdb_ds = gdb_driver.Open(gdb_fn, 1) 16 | else: 17 | gdb_ds = gdb_driver.CreateDataSource(gdb_fn) 18 | if gdb_ds is None: 19 | raise RuntimeError('Could not open file geodatabase') 20 | 21 | # Create an option list so the feature classes will be 22 | # saved in a feature dataset. 23 | options = ['FEATURE_DATASET=' + dataset_name] 24 | 25 | # Loop through the layers in the input datasource and copy 26 | # each one into the geodatabase. 27 | for i in range(in_ds.GetLayerCount()): 28 | lyr = in_ds.GetLayer(i) 29 | lyr_name = lyr.GetName() 30 | print('Copying ' + lyr_name + '...') 31 | gdb_ds.CopyLayer(lyr, lyr_name, options) 32 | -------------------------------------------------------------------------------- /Chapter8/listing8_1.py: -------------------------------------------------------------------------------- 1 | # Script to reproject a shapefile. 2 | 3 | from osgeo import ogr, osr 4 | 5 | # Create an output SRS. 6 | sr = osr.SpatialReference() 7 | sr.ImportFromProj4('''+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=23 8 | +lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 9 | +datum=NAD83 +units=m +no_defs''') 10 | 11 | # Don't forget to change your directory here. 12 | ds = ogr.Open(r'D:\osgeopy-data\US', 1) 13 | 14 | # Get the input layer. 15 | in_lyr = ds.GetLayer('us_volcanos') 16 | 17 | # Create the empty output layer. 18 | out_lyr = ds.CreateLayer('us_volcanos_aea', sr, 19 | ogr.wkbPoint) 20 | out_lyr.CreateFields(in_lyr.schema) 21 | 22 | # Loop through the features in the input layer. 23 | out_feat = ogr.Feature(out_lyr.GetLayerDefn()) 24 | for in_feat in in_lyr: 25 | 26 | # Clone the geometry, project it, and add it to the feature. 27 | geom = in_feat.geometry().Clone() 28 | geom.TransformTo(sr) 29 | out_feat.SetGeometry(geom) 30 | 31 | # Copy attributes. 32 | for i in range(in_feat.GetFieldCount()): 33 | out_feat.SetField(i, in_feat.GetField(i)) 34 | 35 | # Insert the feature 36 | out_lyr.CreateFeature(out_feat) 37 | -------------------------------------------------------------------------------- /Chapter10/listing10_5.txt: -------------------------------------------------------------------------------- 1 | See listing10_5.vrt in the osgeopy-data/Landsat/Washington folder for a working version (because it's in the correct folder). But here's the text that's in it: 2 | 3 | 4 | 5 | PROJCS["WGS 84 / UTM zone 10N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-123],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32610"]] 6 | 7 | 343724.25, 28.5, 0, 5369585.25, 0,-28.5 8 | 9 | 10 | nat_color.tif 11 | 3 12 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter3/listing3_3.py: -------------------------------------------------------------------------------- 1 | # Script to extract certain features from a shapefile and save them to 2 | # another file. 3 | 4 | import sys 5 | from osgeo import ogr 6 | 7 | # Open the folder dataa source for writing 8 | ds = ogr.Open(r'D:\osgeopy-data\global', 1) 9 | if ds is None: 10 | sys.exit('Could not open folder.') 11 | 12 | # Get the input shapefile 13 | in_lyr = ds.GetLayer('ne_50m_populated_places') 14 | 15 | # Create a point layer 16 | if ds.GetLayer('capital_cities'): 17 | ds.DeleteLayer('capital_cities') 18 | out_lyr = ds.CreateLayer('capital_cities', 19 | in_lyr.GetSpatialRef(), 20 | ogr.wkbPoint) 21 | out_lyr.CreateFields(in_lyr.schema) 22 | 23 | # Create a blank feature 24 | out_defn = out_lyr.GetLayerDefn() 25 | out_feat = ogr.Feature(out_defn) 26 | 27 | for in_feat in in_lyr: 28 | if in_feat.GetField('FEATURECLA') == 'Admin-0 capital': 29 | 30 | # Copy geometry and attributes 31 | geom = in_feat.geometry() 32 | out_feat.SetGeometry(geom) 33 | for i in range(in_feat.GetFieldCount()): 34 | value = in_feat.GetField(i) 35 | out_feat.SetField(i, value) 36 | 37 | # Insert the feature 38 | out_lyr.CreateFeature(out_feat) 39 | 40 | # Close files 41 | del ds 42 | -------------------------------------------------------------------------------- /Chapter7/listing7_8.py: -------------------------------------------------------------------------------- 1 | from osgeo import ogr 2 | import ch7funcs 3 | 4 | ds = ogr.Open(r'D:\osgeopy-data\Galapagos', True) 5 | pt_lyr = ds.GetLayerByName('albatross_lambert') 6 | poly_lyr = ds.CreateLayer( 7 | 'albatross_ranges', pt_lyr.GetSpatialRef(), ogr.wkbPolygon) 8 | id_field = ogr.FieldDefn('tag_id', ogr.OFTString) 9 | area_field = ogr.FieldDefn('area', ogr.OFTReal) #A 10 | area_field.SetWidth(30) #A 11 | area_field.SetPrecision(4) #A 12 | poly_lyr.CreateFields([id_field, area_field]) 13 | poly_row = ogr.Feature(poly_lyr.GetLayerDefn()) 14 | 15 | for tag_id in ch7funcs.get_unique(ds, 'albatross_lambert', 'tag_id'): 16 | print('Processing ' + tag_id) 17 | pt_lyr.SetAttributeFilter("tag_id = '{}'".format(tag_id)) 18 | locations = ogr.Geometry(ogr.wkbMultiPoint) #B 19 | for pt_row in pt_lyr: #B 20 | locations.AddGeometry(pt_row.geometry().Clone()) #B 21 | 22 | homerange = locations.ConvexHull() #C 23 | poly_row.SetGeometry(homerange) 24 | poly_row.SetField('tag_id', tag_id) 25 | poly_row.SetField('area', homerange.GetArea()) 26 | poly_lyr.CreateFeature(poly_row) 27 | 28 | del ds 29 | -------------------------------------------------------------------------------- /Chapter7/listing7_1.py: -------------------------------------------------------------------------------- 1 | # Use a flawed method to find out how many US cities are within 16,000 2 | # meters of a volcano. 3 | 4 | from osgeo import ogr 5 | 6 | shp_ds = ogr.Open(r'D:\osgeopy-data\US') 7 | volcano_lyr = shp_ds.GetLayer('us_volcanos_albers') 8 | cities_lyr = shp_ds.GetLayer('cities_albers') 9 | 10 | # Create a temporary layer in memory to store the buffers. 11 | memory_driver = ogr.GetDriverByName('memory') 12 | memory_ds = memory_driver.CreateDataSource('temp') 13 | buff_lyr = memory_ds.CreateLayer('buffer') 14 | buff_feat = ogr.Feature(buff_lyr.GetLayerDefn()) 15 | 16 | # Loop through the volcanoes, buffer each one, and store the result in the 17 | # temporary layer (the tmp variables are there to keep a bunch of zeros from 18 | # printing out when running this in an interactive window-- if you were 19 | # really good, you could check their values to make sure nothing went wrong). 20 | for volcano_feat in volcano_lyr: 21 | buff_geom = volcano_feat.geometry().Buffer(16000) 22 | tmp = buff_feat.SetGeometry(buff_geom) 23 | tmp = buff_lyr.CreateFeature(buff_feat) 24 | 25 | # Once all of the volcanoes have been buffered, intersect the temporary 26 | # buffer layer with the cities layer to get the cities that fall in a buffer. 27 | result_lyr = memory_ds.CreateLayer('result') 28 | buff_lyr.Intersection(cities_lyr, result_lyr) 29 | print('Cities: {}'.format(result_lyr.GetFeatureCount())) 30 | -------------------------------------------------------------------------------- /Chapter11/listing11_5.py: -------------------------------------------------------------------------------- 1 | # Script to compute slope from a DEM. 2 | 3 | import os 4 | import numpy as np 5 | from osgeo import gdal 6 | import ospybook as pb 7 | 8 | in_fn = r"D:\osgeopy-data\Nepal\everest_utm.tif" 9 | out_fn = r'D:\Temp\everest_slope.tif' 10 | 11 | # Get cell width and height. 12 | in_ds = gdal.Open(in_fn) 13 | cell_width = in_ds.GetGeoTransform()[1] 14 | cell_height = in_ds.GetGeoTransform()[5] 15 | 16 | # Read the data into a floating point array. 17 | band = in_ds.GetRasterBand(1) 18 | in_data = band.ReadAsArray().astype(np.float) 19 | 20 | # Initialize the output array with -99. 21 | out_data = np.ones((band.YSize, band.XSize)) * -99 22 | 23 | # Make the slices. 24 | slices = pb.make_slices(in_data, (3, 3)) 25 | 26 | # Compute the slope using the equations from the text. 27 | rise = ((slices[6] + (2 * slices[7]) + slices[8]) - 28 | (slices[0] + (2 * slices[1]) + slices[2])) / \ 29 | (8 * cell_height) 30 | run = ((slices[2] + (2 * slices[5]) + slices[8]) - 31 | (slices[0] + (2 * slices[3]) + slices[6])) / \ 32 | (8 * cell_width) 33 | dist = np.sqrt(np.square(rise) + np.square(run)) 34 | 35 | # The output from the last equation is inserted into the middle 36 | # of the output array, ignoring the edges again. 37 | out_data[1:-1, 1:-1] = np.arctan(dist) * 180 / np.pi 38 | 39 | # Save the data. 40 | pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Float32, -99) 41 | del in_ds 42 | -------------------------------------------------------------------------------- /Chapter11/listing11_9.py: -------------------------------------------------------------------------------- 1 | # Zonal analysis with SciPy 2 | 3 | import os 4 | import numpy as np 5 | import scipy.stats 6 | from osgeo import gdal 7 | 8 | def get_bins(data): 9 | """Return bin edges for all unique values in data.""" 10 | bins = np.unique(data) 11 | return np.append(bins, max(bins) + 1) 12 | 13 | os.chdir(r'D:\osgeopy-data\Utah') 14 | landcover_fn = 'landcover60.tif' 15 | ecoregion_fn = 'utah_ecoIII60.tif' 16 | out_fn = r'D:\Temp\histogram.csv' 17 | 18 | # Read in the ecoregion data and get appropriate bins. 19 | eco_ds = gdal.Open(ecoregion_fn) 20 | eco_band = eco_ds.GetRasterBand(1) 21 | eco_data = eco_band.ReadAsArray().flatten() 22 | eco_bins = get_bins(eco_data) 23 | 24 | # Read in the landcover data and get appropriate bins. 25 | lc_ds = gdal.Open(landcover_fn) 26 | lc_band = lc_ds.GetRasterBand(1) 27 | lc_data = lc_band.ReadAsArray().flatten() 28 | lc_bins = get_bins(lc_data) 29 | 30 | # Calculate the histogram. 31 | hist, eco_bins2, lc_bins2, bn = \ 32 | scipy.stats.binned_statistic_2d( 33 | eco_data, lc_data, lc_data, 'count', 34 | [eco_bins, lc_bins]) 35 | 36 | # Add bin information to the histogram so the output file 37 | # is more useful. 38 | hist = np.insert(hist, 0, lc_bins[:-1], 0) 39 | row_labels = np.insert(eco_bins[:-1], 0, 0) 40 | hist = np.insert(hist, 0, row_labels, 1) 41 | 42 | # Save the output 43 | np.savetxt(out_fn, hist, fmt='%1.0f', delimiter=',') 44 | -------------------------------------------------------------------------------- /Chapter12/listing12_4.py: -------------------------------------------------------------------------------- 1 | # Script to create a confusion matrix and calculate kappa. 2 | 3 | import csv 4 | import os 5 | import numpy as np 6 | from sklearn import metrics 7 | import skll 8 | from osgeo import gdal 9 | 10 | folder = r'D:\osgeopy-data\Utah' 11 | accuracy_fn = 'accuracy_data.csv' 12 | matrix_fn = 'confusion_matrix.csv' 13 | prediction_fn = r'D:\osgeopy-data\Landsat\Utah\tree_prediction60.tif' 14 | 15 | os.chdir(folder) 16 | 17 | # Collect the data needed for the accuracy assessment. 18 | xys = [] 19 | classes = [] 20 | with open(accuracy_fn) as fp: 21 | reader = csv.reader(fp) 22 | next(reader) 23 | for row in reader: 24 | xys.append([float(n) for n in row[:2]]) 25 | classes.append(int(row[2])) 26 | 27 | ds = gdal.Open(prediction_fn) 28 | pixel_trans = gdal.Transformer(ds, None, []) 29 | offset, ok = pixel_trans.TransformPoints(True, xys) 30 | cols, rows, z = zip(*offset) 31 | 32 | data = ds.GetRasterBand(1).ReadAsArray() 33 | sample = data[rows, cols] 34 | del ds 35 | 36 | # Compute kappa. 37 | print('Kappa:', skll.kappa(classes, sample)) 38 | 39 | # Create the confusion matrix. 40 | labels = np.unique(np.concatenate((classes, sample))) 41 | matrix = metrics.confusion_matrix(classes, sample, labels) 42 | 43 | # Add labels to the matrix and save it. 44 | matrix = np.insert(matrix, 0, labels, 0) 45 | matrix = np.insert(matrix, 0, np.insert(labels, 0, 0), 1) 46 | np.savetxt(matrix_fn, matrix, fmt='%1.0f', delimiter=',') 47 | -------------------------------------------------------------------------------- /Chapter7/listing7_5.py: -------------------------------------------------------------------------------- 1 | # Create a shapefile from a CSV. 2 | 3 | from osgeo import ogr, osr 4 | 5 | csv_fn = r"D:\osgeopy-data\Galapagos\Galapagos Albatrosses.csv" 6 | shp_fn = r"D:\osgeopy-data\Galapagos\albatross_dd.shp" 7 | sr = osr.SpatialReference(osr.SRS_WKT_WGS84) 8 | 9 | # Create the shapefile with two attribute fields. 10 | shp_ds = ogr.GetDriverByName('ESRI Shapefile').CreateDataSource(shp_fn) 11 | shp_lyr = shp_ds.CreateLayer('albatross_dd', sr, ogr.wkbPoint) 12 | shp_lyr.CreateField(ogr.FieldDefn('tag_id', ogr.OFTString)) 13 | shp_lyr.CreateField(ogr.FieldDefn('timestamp', ogr.OFTString)) 14 | shp_row = ogr.Feature(shp_lyr.GetLayerDefn()) 15 | 16 | # Open the csv and loop through each row. 17 | csv_ds = ogr.Open(csv_fn) 18 | csv_lyr = csv_ds.GetLayer() 19 | for csv_row in csv_lyr: 20 | 21 | # Get the x,y coordinates from the csv and create a point geometry. 22 | x = csv_row.GetFieldAsDouble('location-long') 23 | y = csv_row.GetFieldAsDouble('location-lat') 24 | shp_pt = ogr.Geometry(ogr.wkbPoint) 25 | shp_pt.AddPoint(x, y) 26 | 27 | # Get the attribute data from the csv. 28 | tag_id = csv_row.GetField('individual-local-identifier') 29 | timestamp = csv_row.GetField('timestamp') 30 | 31 | # Add the data to the shapefile. 32 | shp_row.SetGeometry(shp_pt) 33 | shp_row.SetField('tag_id', tag_id) 34 | shp_row.SetField('timestamp', timestamp) 35 | shp_lyr.CreateFeature(shp_row) 36 | 37 | del csv_ds, shp_ds 38 | -------------------------------------------------------------------------------- /Chapter13/listing13_14.py: -------------------------------------------------------------------------------- 1 | # Script to draw a topo map with a hillshade underneath. 2 | 3 | import mapnik 4 | srs = '+proj=utm +zone=10 +ellps=GRS80 +datum=NAD83 +units=m +no_defs' 5 | m = mapnik.Map(1200, 1200, srs) 6 | m.zoom_to_box(mapnik.Box2d(558800, 5112200, 566600, 5120500)) 7 | 8 | hillshade_lyr = mapnik.Layer('Hillshade', srs) 9 | hillshade_raster = mapnik.Gdal( 10 | file=r'D:\osgeopy-data\Washington\dem\sthelens_hillshade.tif') 11 | hillshade_lyr.datasource = hillshade_raster 12 | 13 | hillshade_rule = mapnik.Rule() 14 | hillshade_rule.symbols.append(mapnik.RasterSymbolizer()) 15 | hillshade_style = mapnik.Style() 16 | hillshade_style.rules.append(hillshade_rule) 17 | 18 | m.append_style('hillshade', hillshade_style) 19 | hillshade_lyr.styles.append('hillshade') 20 | 21 | topo_lyr = mapnik.Layer('Topo', srs) 22 | topo_raster = mapnik.Gdal( 23 | file=r'D:\osgeopy-data\Washington\dem\st_helens.tif') 24 | topo_lyr.datasource = topo_raster 25 | 26 | topo_sym = mapnik.RasterSymbolizer() 27 | topo_sym.opacity = 0.6 #A 28 | topo_rule = mapnik.Rule() 29 | topo_rule.symbols.append(topo_sym) 30 | topo_style = mapnik.Style() 31 | topo_style.rules.append(topo_rule) 32 | 33 | m.append_style('topo', topo_style) 34 | topo_lyr.styles.append('topo') 35 | 36 | m.layers.append(hillshade_lyr) #B 37 | m.layers.append(topo_lyr) #B 38 | mapnik.render_to_file(m, r'd:\temp\helens2.png') 39 | -------------------------------------------------------------------------------- /Chapter13/listing13_2.py: -------------------------------------------------------------------------------- 1 | # Plot countries as multipolygons. 2 | 3 | import matplotlib.pyplot as plt 4 | from osgeo import ogr 5 | 6 | def plot_polygon(poly, symbol='k-', **kwargs): 7 | """Plots a polygon using the given symbol.""" 8 | for i in range(poly.GetGeometryCount()): 9 | subgeom = poly.GetGeometryRef(i) 10 | x, y = zip(*subgeom.GetPoints()) 11 | plt.plot(x, y, symbol, **kwargs) 12 | 13 | def plot_layer(filename, symbol, layer_index=0, **kwargs): 14 | """Plots an OGR polygon layer using the given symbol.""" 15 | ds = ogr.Open(filename) 16 | 17 | # Loop through all of the features in the layer. 18 | for row in ds.GetLayer(layer_index): 19 | geom = row.geometry() 20 | geom_type = geom.GetGeometryType() 21 | 22 | # If the geometry is a single polygon. 23 | if geom_type == ogr.wkbPolygon: 24 | plot_polygon(geom, symbol, **kwargs) 25 | 26 | # Else if the geometry is a multipolygon, send each 27 | # part to plot_polygon individually. 28 | elif geom_type == ogr.wkbMultiPolygon: 29 | for i in range(geom.GetGeometryCount()): 30 | subgeom = geom.GetGeometryRef(i) 31 | plot_polygon(subgeom, symbol, **kwargs) 32 | 33 | # Plot countries. 34 | plot_layer(r'D:\osgeopy-data\global\ne_110m_admin_0_countries.shp', 'k-') 35 | plt.axis('equal') 36 | 37 | # Get rid of the tick marks on the side of the plot. 38 | plt.gca().get_xaxis().set_ticks([]) 39 | plt.gca().get_yaxis().set_ticks([]) 40 | plt.show() 41 | -------------------------------------------------------------------------------- /Chapter6/listing6_1.py: -------------------------------------------------------------------------------- 1 | def line_to_point_layer(ds, line_name, pt_name): 2 | """Creates a point layer from vertices in a line layer.""" 3 | # Delete the point layer if it exists. 4 | if ds.GetLayer(pt_name): 5 | ds.DeleteLayer(pt_name) 6 | 7 | # Get the line layer and its spatial reference. 8 | line_lyr = ds.GetLayer(line_name) 9 | sr = line_lyr.GetSpatialRef() 10 | 11 | # Create a point layer with the same SR as the lines 12 | # and copy the field definitions from the line to 13 | # the point layer. 14 | pt_lyr = ds.CreateLayer(pt_name, sr, ogr.wkbPoint) 15 | pt_lyr.CreateFields(line_lyr.schema) 16 | 17 | # Create a feature and geometry to use over and over. 18 | pt_feat = ogr.Feature(pt_lyr.GetLayerDefn()) 19 | pt_geom = ogr.Geometry(ogr.wkbPoint) 20 | 21 | # Loop through all of the lines. 22 | for line_feat in line_lyr: 23 | 24 | # Copy the attribute values from the line to the 25 | # new feature. 26 | atts = line_feat.items() 27 | for fld_name in atts.keys(): 28 | pt_feat.SetField(fld_name, atts[fld_name]) 29 | 30 | # Loop through the line's vertices and for each one, 31 | # set the coordinates on the point geometry, add 32 | # it to the feature, and use the feature to create 33 | # a new feature in the point layer. 34 | for coords in line_feat.geometry().GetPoints(): 35 | pt_geom.AddPoint(*coords) 36 | pt_feat.SetGeometry(pt_geom) 37 | pt_lyr.CreateFeature(pt_feat) 38 | -------------------------------------------------------------------------------- /Chapter13/listing13_7.py: -------------------------------------------------------------------------------- 1 | # Script that uses meshgrid to get map coordinates and then plots 2 | # the DEM in 3d. 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D 7 | from osgeo import gdal 8 | 9 | 10 | ds = gdal.Open(r'D:\osgeopy-data\Washington\dem\sthelens_utm.tif') 11 | band = ds.GetRasterBand(1) 12 | ov_band = band.GetOverview(band.GetOverviewCount() - 3) 13 | data = ov_band.ReadAsArray() 14 | 15 | # Calculate bounding coordinates. 16 | geotransform = ds.GetGeoTransform() 17 | minx = geotransform[0] 18 | maxy = geotransform[3] 19 | maxx = minx + ov_band.XSize * geotransform[1] 20 | miny = maxy + ov_band.YSize * geotransform[5] 21 | 22 | # Get the x and y arrays. 23 | x = np.arange(minx, maxx, geotransform[1]) 24 | y = np.arange(maxy, miny, geotransform[5]) 25 | x, y = np.meshgrid(x[:ov_band.XSize], y[:ov_band.YSize]) 26 | 27 | # Make the 3D plot. 28 | fig = plt.figure() 29 | ax = fig.gca(projection='3d') 30 | ax.plot_surface(x, y, data, cmap='gist_earth', lw=0) 31 | plt.axis('equal') 32 | 33 | # # Change the viewpoint and turn the ticks off. 34 | # ax.view_init(elev=55, azim=60) 35 | # plt.axis('off') 36 | 37 | # # Create an animation. 38 | # import matplotlib.animation as animation 39 | # def animate(i): 40 | # ax.view_init(elev=65, azim=i) 41 | # anim = animation.FuncAnimation( 42 | # fig, animate, frames=range(0, 360, 10), interval=100) 43 | # plt.axis('off') 44 | 45 | # # If you have FFmpeg and it's in your path, you can save the 46 | # # animation. 47 | # anim.save('d:/temp/helens.mp4', 'ffmpeg') 48 | 49 | plt.show() 50 | -------------------------------------------------------------------------------- /Chapter10/listing10_4.py: -------------------------------------------------------------------------------- 1 | # Script to add an attribute table to a raster. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Don't forget to change the folder. 7 | os.chdir(r'D:\osgeopy-data\Switzerland') 8 | 9 | # Open the output from listing 9.3 and get the band. 10 | ds = gdal.Open('dem_class2.tif') 11 | band = ds.GetRasterBand(1) 12 | 13 | # Change the NoData value to -1 so that the histogram will be computed 14 | # using 0 values. 15 | band.SetNoDataValue(-1) 16 | 17 | # Create the raster attribute table and add 3 columns for the pixel value, 18 | # number of pixels with that value, and elevation label. 19 | rat = gdal.RasterAttributeTable() 20 | rat.CreateColumn('Value', gdal.GFT_Integer, gdal.GFU_Name) 21 | rat.CreateColumn('Count', gdal.GFT_Integer, gdal.GFU_PixelCount) 22 | rat.CreateColumn('Elevation', gdal.GFT_String, gdal.GFU_Generic) 23 | 24 | # Add 6 rows to the table, for values 0-5. 25 | rat.SetRowCount(6) 26 | 27 | # Write the values 0-5 (using range) to the first column (pixel value). 28 | rat.WriteArray(range(6), 0) 29 | 30 | # Get the histogram and write the results to the second column (count). 31 | rat.WriteArray(band.GetHistogram(-0.5, 5.5, 6, False, False), 1) 32 | 33 | # Add the labels for each pixel value to the third column. 34 | rat.SetValueAsString(1, 2, '0 - 800') 35 | rat.SetValueAsString(2, 2, '800 - 1300') 36 | rat.SetValueAsString(3, 2, '1300 - 2000') 37 | rat.SetValueAsString(4, 2, '2000 - 2600') 38 | rat.SetValueAsString(5, 2, '2600 +') 39 | 40 | # Add the attribute table to the raster and restore the NoData value. 41 | band.SetDefaultRAT(rat) 42 | band.SetNoDataValue(0) 43 | del ds 44 | -------------------------------------------------------------------------------- /Chapter10/listing10_1.py: -------------------------------------------------------------------------------- 1 | # Script to add ground control points to a raster. 2 | 3 | import os 4 | import shutil 5 | from osgeo import gdal, osr 6 | 7 | # Don't forget to change the directory. 8 | # Make a copy of the original image so we're leaving it alone and changing 9 | # the new one. Try opening the original in a GIS. It doesn't have any 10 | # SRS info and the upper left corner should have coordinates of 0,0. 11 | os.chdir(r'D:\osgeopy-data\Utah') 12 | shutil.copy('cache_no_gcp.tif', 'cache.tif') 13 | 14 | # Open the copied image so we can add GCPs to it. 15 | ds = gdal.Open('cache.tif', gdal.GA_Update) 16 | 17 | # Create the SRS that the GCP coordinates use. 18 | sr = osr.SpatialReference() 19 | sr.SetWellKnownGeogCS('WGS84') 20 | 21 | # Create the list of GCPs. 22 | gcps = [gdal.GCP(-111.931075, 41.745836, 0, 1078, 648), 23 | gdal.GCP(-111.901655, 41.749269, 0, 3531, 295), 24 | gdal.GCP(-111.899180, 41.739882, 0, 3722, 1334), 25 | gdal.GCP(-111.930510, 41.728719, 0, 1102, 2548)] 26 | 27 | # Add the GCPs to the raster 28 | ds.SetGCPs(gcps, sr.ExportToWkt()) 29 | ds.SetProjection(sr.ExportToWkt()) 30 | ds = None 31 | 32 | 33 | 34 | ############################################################################### 35 | 36 | # This time we'll use the driver to make a copy of the raster and then add 37 | # a geotransform instead of GCPs. This still uses the sr and gcps variables 38 | # from above. 39 | old_ds = gdal.Open('cache_no_gcp.tif') 40 | ds = old_ds.GetDriver().CreateCopy('cache2.tif', old_ds) 41 | ds.SetProjection(sr.ExportToWkt()) 42 | ds.SetGeoTransform(gdal.GCPsToGeoTransform(gcps)) 43 | del ds, old_ds 44 | -------------------------------------------------------------------------------- /Chapter9/listing9_4.py: -------------------------------------------------------------------------------- 1 | # Script to resample a raster to a smaller pixel size. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Don't forget to change the folder. 7 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 8 | 9 | # Open the input raster. 10 | in_ds = gdal.Open('p047r027_7t20000730_z10_nn10.tif') 11 | in_band = in_ds.GetRasterBand(1) 12 | 13 | # Computer the number of output rows and columns (double the input numbers 14 | # because we're cutting pixel size in half). 15 | out_rows = in_band.YSize * 2 16 | out_columns = in_band.XSize * 2 17 | 18 | # Create the output raster using the computed dimensions. 19 | gtiff_driver = gdal.GetDriverByName('GTiff') 20 | out_ds = gtiff_driver.Create('band1_resampled.tif', 21 | out_columns, out_rows) 22 | 23 | # Change the geotransform so it reflects the smaller cell size before 24 | # setting it onto the output. 25 | out_ds.SetProjection(in_ds.GetProjection()) 26 | geotransform = list(in_ds.GetGeoTransform()) 27 | geotransform[1] /= 2 28 | geotransform[5] /= 2 29 | out_ds.SetGeoTransform(geotransform) 30 | 31 | # Read in the data, but have gdal resample it so that it has the specified 32 | # number of rows and columns instead of the numbers that the input has. 33 | # This effectively resizes the pixels. 34 | data = in_band.ReadAsArray( 35 | buf_xsize=out_columns, buf_ysize=out_rows) 36 | 37 | # Write the data to the output raster. 38 | out_band = out_ds.GetRasterBand(1) 39 | out_band.WriteArray(data) 40 | 41 | # Compute statistics and build overviews. 42 | out_band.FlushCache() 43 | out_band.ComputeStatistics(False) 44 | out_ds.BuildOverviews('average', [2, 4, 8, 16, 32, 64]) 45 | 46 | del out_ds 47 | -------------------------------------------------------------------------------- /Chapter6/listing6_2.py: -------------------------------------------------------------------------------- 1 | def poly_to_line_layer(ds, poly_name, line_name): 2 | """Creates a line layer from a polygon layer.""" 3 | # Delete the line layer if it exists. 4 | if ds.GetLayer(line_name): 5 | ds.DeleteLayer(line_name) 6 | 7 | # Get the polygon layer and its spatial reference. 8 | poly_lyr = ds.GetLayer(poly_name) 9 | sr = poly_lyr.GetSpatialRef() 10 | 11 | # Create a line layer with the same SR as the polygons 12 | # and copy the field definitions from the polygons to 13 | # the line layer. 14 | line_lyr = ds.CreateLayer(line_name, sr, ogr.wkbLineString) 15 | line_lyr.CreateFields(poly_lyr.schema) 16 | 17 | # Create a feature to use over and over. 18 | line_feat = ogr.Feature(line_lyr.GetLayerDefn()) 19 | 20 | # Loop through all of the polygons. 21 | for poly_feat in poly_lyr: 22 | 23 | # Copy the attribute values from the polygon to the 24 | # new feature. 25 | atts = poly_feat.items() 26 | for fld_name in atts.keys(): 27 | line_feat.SetField(fld_name, atts[fld_name]) 28 | 29 | # Loop through the rings in the polygon. 30 | poly_geom = poly_feat.geometry() 31 | for i in range(poly_geom.GetGeometryCount()): 32 | ring = poly_geom.GetGeometryRef(i) 33 | 34 | # Create a new line using the ring's vertices. 35 | line_geom = ogr.Geometry(ogr.wkbLineString) 36 | for coords in ring.GetPoints(): 37 | line_geom.AddPoint(*coords) 38 | 39 | # Insert the new line feature. 40 | line_feat.SetGeometry(line_geom) 41 | line_lyr.CreateFeature(line_feat) 42 | -------------------------------------------------------------------------------- /Chapter7/listing7_7.py: -------------------------------------------------------------------------------- 1 | # Use GPS locations and elapsed time to get maximum speeds. 2 | 3 | from datetime import datetime 4 | from osgeo import ogr 5 | import ch7funcs 6 | 7 | date_format = '%Y-%m-%d %H:%M:%S.%f' 8 | ds = ogr.Open(r'D:\osgeopy-data\Galapagos') 9 | lyr = ds.GetLayerByName('albatross_lambert') 10 | 11 | # Loop through each tag, initialize max_speed to 0, and limit the GPS 12 | # locations to that tag. 13 | for tag_id in ch7funcs.get_unique(ds, 'albatross_lambert', 'tag_id'): 14 | max_speed = 0 15 | lyr.SetAttributeFilter("tag_id ='{}'".format(tag_id)) 16 | 17 | # Get the timestamp for the first point and convert it to a datetime. 18 | row = next(lyr) 19 | ts = row.GetField('timestamp') 20 | previous_time = datetime.strptime(ts, date_format) 21 | 22 | # Loop through the rest of the locations for the current tag. 23 | for row in lyr: 24 | 25 | # Get the current timestamp, convert to a datetime, and calculate 26 | # the amount of time since the previous location. 27 | ts = row.GetField('timestamp') 28 | current_time = datetime.strptime(ts, date_format) 29 | elapsed_time = current_time - previous_time 30 | hours = elapsed_time.total_seconds() / 3600 31 | 32 | # Use the distance you calculated in listing 7.6 to calculate speed. 33 | distance = row.GetField('distance') 34 | speed = distance / hours 35 | 36 | # Keep this speed if it's the largest seen so far. 37 | max_speed = max(max_speed, speed) 38 | 39 | # When done looping through the locations for this tag, print out the 40 | # max speed. 41 | print 'Max speed for {0}: {1}'.format(tag_id, max_speed) 42 | -------------------------------------------------------------------------------- /Chapter7/listing7_6.py: -------------------------------------------------------------------------------- 1 | # Calculate distance between adjacent points. 2 | 3 | from osgeo import ogr 4 | import ch7funcs 5 | 6 | # Open the layer and add a distance field. 7 | ds = ogr.Open(r'D:\osgeopy-data\Galapagos', True) 8 | lyr = ds.GetLayerByName('albatross_lambert') 9 | lyr.CreateField(ogr.FieldDefn('distance', ogr.OFTReal)) 10 | 11 | 12 | # Get the unique tags. Notice this uses the ch7funcs module but the text 13 | # assumes the function is in the same script. 14 | tag_ids = ch7funcs.get_unique(ds, 'albatross_lambert', 'tag_id') 15 | 16 | # Loop through the IDs. 17 | for tag_id in tag_ids: 18 | print('Processing ' + tag_id) 19 | 20 | # Limit the GPS points to the ones with the current tag ID. 21 | lyr.SetAttributeFilter( 22 | "tag_id ='{}'".format(tag_id)) 23 | 24 | # Get the point and timestamp for the first location. 25 | row = next(lyr) 26 | previous_pt = row.geometry().Clone() 27 | previous_time = row.GetField('timestamp') 28 | 29 | # Loop the rest of the locations for the current tag. 30 | for row in lyr: 31 | current_time = row.GetField('timestamp') 32 | if current_time < previous_time: 33 | raise Exception('Timestamps out of order') 34 | 35 | # Calculate the distance to the previous point and save it. 36 | current_pt = row.geometry().Clone() 37 | distance = current_pt.Distance(previous_pt) 38 | row.SetField('distance', distance) 39 | lyr.SetFeature(row) 40 | 41 | # Remember the current point so it can be used as the "previous" 42 | # one when processing the next location. 43 | previous_pt = current_pt 44 | previous_time = current_time 45 | del ds 46 | -------------------------------------------------------------------------------- /Chapter7/listing7_4.py: -------------------------------------------------------------------------------- 1 | # Script to combine small polygons from the wind farm example. 2 | from osgeo import ogr 3 | 4 | # Open the original output layer and create a new one. 5 | folder = r'D:\osgeopy-data\California' 6 | ds = ogr.Open(folder, True) 7 | in_lyr = ds.GetLayerByName('wind_farm') 8 | out_lyr = ds.CreateLayer( 9 | 'wind_farm2', in_lyr.GetSpatialRef(), ogr.wkbPolygon) 10 | out_row = ogr.Feature(out_lyr.GetLayerDefn()) 11 | 12 | # Create a multipolygon to hold the small polygons to be combined. 13 | multipoly = ogr.Geometry(ogr.wkbMultiPolygon) 14 | 15 | # Loop through the rows in the original output and get the geometry. 16 | for in_row in in_lyr: 17 | in_geom = in_row.geometry().Clone() 18 | in_geom_type = in_geom.GetGeometryType() 19 | 20 | # If the geometry is a polygon, go ahead and add it to the multipolygon. 21 | if in_geom_type == ogr.wkbPolygon: 22 | multipoly.AddGeometry(in_geom) 23 | 24 | # But if it's a multipoly, break it up and add each polygon individually. 25 | elif in_geom_type == ogr.wkbMultiPolygon: 26 | for i in range(in_geom.GetGeometryCount()): 27 | multipoly.AddGeometry( 28 | in_geom.GetGeometryRef(i)) 29 | 30 | # Union all of the small polygons together. This will create a new 31 | # multipolygon. 32 | multipoly = multipoly.UnionCascaded() 33 | 34 | # Now loop through the single polygons contained in the unioned multipolygon 35 | # and write them out to the new layer if they have a large enough area. 36 | for i in range(multipoly.GetGeometryCount()): 37 | poly = multipoly.GetGeometryRef(i) 38 | if poly.GetArea() > 1000000: 39 | out_row.SetGeometry(poly) 40 | out_lyr.CreateFeature(out_row) 41 | del ds 42 | -------------------------------------------------------------------------------- /Chapter9/listing9_1.py: -------------------------------------------------------------------------------- 1 | # Script to stack three 1-band rasters into a a single 3-band image. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Be sure to change your directory. 7 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 8 | band1_fn = 'p047r027_7t20000730_z10_nn10.tif' 9 | band2_fn = 'p047r027_7t20000730_z10_nn20.tif' 10 | band3_fn = 'p047r027_7t20000730_z10_nn30.tif' 11 | 12 | # Open band 1. 13 | in_ds = gdal.Open(band1_fn) 14 | in_band = in_ds.GetRasterBand(1) 15 | 16 | # Create a 3-band GeoTIFF with the same dimensions, data type, projection, 17 | # and georeferencing info as band 1. This will be overwritten if it exists. 18 | gtiff_driver = gdal.GetDriverByName('GTiff') 19 | out_ds = gtiff_driver.Create('nat_color.tif', 20 | in_band.XSize, in_band.YSize, 3, in_band.DataType) 21 | out_ds.SetProjection(in_ds.GetProjection()) 22 | out_ds.SetGeoTransform(in_ds.GetGeoTransform()) 23 | 24 | # Copy data from band 1 into the output image. 25 | in_data = in_band.ReadAsArray() 26 | out_band = out_ds.GetRasterBand(3) 27 | out_band.WriteArray(in_data) 28 | 29 | # Copy data from band 2 into the output image. 30 | in_ds = gdal.Open(band2_fn) 31 | out_band = out_ds.GetRasterBand(2) 32 | out_band.WriteArray(in_ds.ReadAsArray()) 33 | 34 | # Copy data from band 3 into the output image. 35 | out_ds.GetRasterBand(1).WriteArray( 36 | gdal.Open(band3_fn).ReadAsArray()) 37 | 38 | # Compute statistics on each output band. 39 | out_ds.FlushCache() 40 | for i in range(1, 4): 41 | out_ds.GetRasterBand(i).ComputeStatistics(False) 42 | 43 | # Build overview layers for faster display. 44 | out_ds.BuildOverviews('average', [2, 4, 8, 16, 32]) 45 | 46 | # This will effectively close the file and flush data to disk. 47 | del out_ds 48 | -------------------------------------------------------------------------------- /Chapter9/listing9_5.py: -------------------------------------------------------------------------------- 1 | # Script to resample a raster to a larger pixel size using byte sequences. 2 | 3 | import os 4 | import numpy as np 5 | from osgeo import gdal 6 | 7 | # Don't forget to change the folder. 8 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 9 | 10 | # Open the input raster. 11 | in_ds = gdal.Open('nat_color.tif') 12 | 13 | # Computer the number of output rows and columns (half the input numbers 14 | # because we're making the pixels twice as big). 15 | out_rows = int(in_ds.RasterYSize / 2) 16 | out_columns = int(in_ds.RasterXSize / 2) 17 | num_bands = in_ds.RasterCount 18 | 19 | # Create the output raster using the computed dimensions. 20 | gtiff_driver = gdal.GetDriverByName('GTiff') 21 | out_ds = gtiff_driver.Create('nat_color_resampled.tif', 22 | out_columns, out_rows, num_bands) 23 | 24 | # Change the geotransform so it reflects the larger cell size before 25 | # setting it onto the output. 26 | out_ds.SetProjection(in_ds.GetProjection()) 27 | geotransform = list(in_ds.GetGeoTransform()) 28 | geotransform[1] *= 2 29 | geotransform[5] *= 2 30 | out_ds.SetGeoTransform(geotransform) 31 | 32 | # Read in the data for all bands, but have gdal resample it so that it has 33 | # the specified number of rows and columns instead of the numbers that the 34 | # input has. This effectively resizes the pixels. 35 | data = in_ds.ReadRaster( 36 | buf_xsize=out_columns, buf_ysize=out_rows) 37 | 38 | # Write the data to the output raster. 39 | out_ds.WriteRaster(0, 0, out_columns, out_rows, data) 40 | 41 | # Compute statistics and build overviews. 42 | out_ds.FlushCache() 43 | for i in range(num_bands): 44 | out_ds.GetRasterBand(i + 1).ComputeStatistics(False) 45 | out_ds.BuildOverviews('average', [2, 4, 8, 16]) 46 | 47 | del out_ds 48 | -------------------------------------------------------------------------------- /Chapter11/listing11_7.py: -------------------------------------------------------------------------------- 1 | # Script to calculate slope using SciPy. 2 | 3 | import os 4 | import numpy as np 5 | import scipy.ndimage 6 | from osgeo import gdal 7 | import ospybook as pb 8 | 9 | in_fn = r"D:\osgeopy-data\Nepal\everest_utm.tif" 10 | out_fn = r'D:\Temp\everest_slope_scipy2.tif' 11 | 12 | # Write a function that calculates slope using a 3x3 window. 13 | # This will be passed to the SciPy filter function below. 14 | def slope(data, cell_width, cell_height): 15 | """Calculates slope using a 3x3 window. 16 | 17 | data - 1D array containing the 9 pixel values, starting 18 | in the upper left and going left to right and down 19 | cell_width - pixel width in the same units as the data 20 | cell_height - pixel height in the same units as the data 21 | """ 22 | rise = ((data[6] + (2 * data[7]) + data[8]) - 23 | (data[0] + (2 * data[1]) + data[2])) / \ 24 | (8 * cell_height) 25 | run = ((data[2] + (2 * data[5]) + data[8]) - 26 | (data[0] + (2 * data[3]) + data[6])) / \ 27 | (8 * cell_width) 28 | dist = np.sqrt(np.square(rise) + np.square(run)) 29 | return np.arctan(dist) * 180 / np.pi 30 | 31 | # Read the data as floating point. 32 | in_ds = gdal.Open(in_fn) 33 | in_band = in_ds.GetRasterBand(1) 34 | in_data = in_band.ReadAsArray().astype(np.float32) 35 | 36 | cell_width = in_ds.GetGeoTransform()[1] 37 | cell_height = in_ds.GetGeoTransform()[5] 38 | 39 | # Pass your slope function, along with the pixel size, to the 40 | # generic_filter function. This will apply your function to 41 | # each window, and each time it will pass the extra arguments 42 | # in to the function along with the data. 43 | out_data = scipy.ndimage.filters.generic_filter( 44 | in_data, slope, size=3, mode='nearest', 45 | extra_arguments=(cell_width, cell_height)) 46 | 47 | # Save the output 48 | pb.make_raster(in_ds, out_fn, out_data, gdal.GDT_Float32) 49 | del in_ds 50 | -------------------------------------------------------------------------------- /Chapter9/listing9_2.py: -------------------------------------------------------------------------------- 1 | # Script to convert an elevation raster from meters to feet, 2 | # one block at a time. 3 | 4 | import os 5 | import numpy as np 6 | from osgeo import gdal 7 | 8 | # Don't forget to change your directory. 9 | os.chdir(r'D:\osgeopy-data\Washington\dem') 10 | 11 | # Open the input raster and get its dimensions. 12 | in_ds = gdal.Open('gt30w140n90.tif') 13 | in_band = in_ds.GetRasterBand(1) 14 | xsize = in_band.XSize 15 | ysize = in_band.YSize 16 | 17 | # Get the block size and NoData value. 18 | block_xsize, block_ysize = in_band.GetBlockSize() 19 | nodata = in_band.GetNoDataValue() 20 | 21 | # Create an output file with the same dimensions and data type. 22 | out_ds = in_ds.GetDriver().Create( 23 | 'dem_feet.tif', xsize, ysize, 1, in_band.DataType) 24 | out_ds.SetProjection(in_ds.GetProjection()) 25 | out_ds.SetGeoTransform(in_ds.GetGeoTransform()) 26 | out_band = out_ds.GetRasterBand(1) 27 | 28 | # Loop through the blocks in the x direction. 29 | for x in range(0, xsize, block_xsize): 30 | 31 | # Get the number of columns to read. 32 | if x + block_xsize < xsize: 33 | cols = block_xsize 34 | else: 35 | cols = xsize - x 36 | 37 | # Loop through the blocks in the y direction. 38 | for y in range(0, ysize, block_ysize): 39 | 40 | # Get the number of rows to read. 41 | if y + block_ysize < ysize: 42 | rows = block_ysize 43 | else: 44 | rows = ysize - y 45 | 46 | # Read in one block's worth of data, convert it to feet, and then 47 | # write the results out to the same block location in the output. 48 | data = in_band.ReadAsArray(x, y, cols, rows) 49 | data = np.where(data == nodata, nodata, data * 3.28084) 50 | out_band.WriteArray(data, x, y) 51 | 52 | # Compute statistics after flushing the cache and setting the NoData value. 53 | out_band.FlushCache() 54 | out_band.SetNoDataValue(nodata) 55 | out_band.ComputeStatistics(False) 56 | out_ds.BuildOverviews('average', [2, 4, 8, 16, 32]) 57 | del out_ds 58 | -------------------------------------------------------------------------------- /Chapter11/listing11_8.py: -------------------------------------------------------------------------------- 1 | # Script to run a smoothing filter in chunks. 2 | 3 | import os 4 | import numpy as np 5 | from osgeo import gdal 6 | import ospybook as pb 7 | 8 | in_fn = r"D:\osgeopy-data\Nepal\everest.tif" 9 | out_fn = r'D:\Temp\everest_smoothed_chunks.tif' 10 | 11 | # Open the input. 12 | in_ds = gdal.Open(in_fn) 13 | in_band = in_ds.GetRasterBand(1) 14 | xsize = in_band.XSize 15 | ysize = in_band.YSize 16 | 17 | # Create the empty output raster. 18 | driver = gdal.GetDriverByName('GTiff') 19 | out_ds = driver.Create(out_fn, xsize, ysize, 1, gdal.GDT_Int32) 20 | out_ds.SetProjection(in_ds.GetProjection()) 21 | out_ds.SetGeoTransform(in_ds.GetGeoTransform()) 22 | out_band = out_ds.GetRasterBand(1) 23 | out_band.SetNoDataValue(-99) 24 | 25 | # Loop through the rows 100 at a time. 26 | n = 100 27 | for i in range(0, ysize, n): 28 | 29 | # Figure out how many rows can be read. Remember we want to read n + 2 30 | # rows if possible. 31 | if i + n + 1 < ysize: 32 | rows = n + 2 33 | else: 34 | rows = ysize - i 35 | 36 | # This makes sure we don't try to read off the top edge the first time 37 | # through. 38 | yoff = max(0, i - 1) 39 | 40 | # Read and process the data as before. 41 | in_data = in_band.ReadAsArray(0, yoff, xsize, rows) 42 | slices = pb.make_slices(in_data, (3, 3)) 43 | stacked_data = np.ma.dstack(slices) 44 | out_data = np.ones(in_data.shape, np.int32) * -99 45 | out_data[1:-1, 1:-1] = np.mean(stacked_data, 2) 46 | 47 | # If it's the first time through, write the entire output array 48 | # starting at the first row. Otherwise, don't write the first row of 49 | # the output array because we don't want to overwrite good data from 50 | # the previous chunk. Because we're ignoring this first row, the row 51 | # offset needs to be increased. 52 | if yoff == 0: 53 | out_band.WriteArray(out_data) 54 | else: 55 | out_band.WriteArray(out_data[1:], 0, yoff + 1) 56 | 57 | # Finish up. 58 | out_band.FlushCache() 59 | out_band.ComputeStatistics(False) 60 | 61 | del out_ds, in_ds 62 | -------------------------------------------------------------------------------- /Chapter13/listing13_4.py: -------------------------------------------------------------------------------- 1 | # Script to draw world countries as patches. 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from matplotlib.path import Path 6 | import matplotlib.patches as patches 7 | from osgeo import ogr 8 | 9 | def order_coords(coords, clockwise): 10 | """Orders coordinates.""" 11 | total = 0 12 | x1, y1 = coords[0] 13 | for x, y in coords[1:]: 14 | total += (x - x1) * (y + y1) 15 | x1, y1 = x, y 16 | x, y = coords[0] 17 | total += (x - x1) * (y + y1) 18 | is_clockwise = total > 0 19 | if clockwise != is_clockwise: 20 | coords.reverse() 21 | return coords 22 | 23 | def make_codes(n): 24 | """Makes a list of path codes.""" 25 | codes = [Path.LINETO] * n 26 | codes[0] = Path.MOVETO 27 | return codes 28 | 29 | def plot_polygon_patch(poly, color): 30 | """Plots a polygon as a patch.""" 31 | # Outer clockwise path. 32 | coords = poly.GetGeometryRef(0).GetPoints() 33 | coords = order_coords(coords, True) 34 | codes = make_codes(len(coords)) 35 | for i in range(1, poly.GetGeometryCount()): 36 | 37 | # Inner counter-clockwise paths. 38 | coords2 = poly.GetGeometryRef(i).GetPoints() 39 | coords2 = order_coords(coords2, False) 40 | codes2 = make_codes(len(coords2)) 41 | 42 | # Concatenate the paths. 43 | coords = np.concatenate((coords, coords2)) 44 | codes = np.concatenate((codes, codes2)) 45 | 46 | # Add the patch to the plot 47 | path = Path(coords, codes) 48 | patch = patches.PathPatch(path, facecolor=color) 49 | plt.axes().add_patch(patch) 50 | 51 | # Loop through all of the features in the countries layer and create 52 | # patches for the polygons. 53 | ds = ogr.Open(r'D:\osgeopy-data\global\ne_110m_admin_0_countries.shp') 54 | lyr = ds.GetLayer(0) 55 | for row in lyr: 56 | geom = row.geometry() 57 | if geom.GetGeometryType() == ogr.wkbPolygon: 58 | plot_polygon_patch(geom, 'yellow') 59 | elif geom.GetGeometryType() == ogr.wkbMultiPolygon: 60 | for i in range(geom.GetGeometryCount()): 61 | plot_polygon_patch(geom.GetGeometryRef(i), 'yellow') 62 | plt.axis('equal') 63 | plt.show() 64 | -------------------------------------------------------------------------------- /Chapter12/listing12_3.py: -------------------------------------------------------------------------------- 1 | # Script to run a map classificatin using a classification tree. 2 | 3 | import csv 4 | import os 5 | import numpy as np 6 | from sklearn import tree 7 | from osgeo import gdal 8 | import ospybook as pb 9 | 10 | folder = r'D:\osgeopy-data\Landsat\Utah' 11 | raster_fns = ['LE70380322000181EDC02_60m.tif', 12 | 'LE70380322000181EDC02_TIR_60m.tif'] 13 | out_fn = 'tree_prediction60.tif' 14 | train_fn = r'D:\osgeopy-data\Utah\training_data.csv' 15 | gap_fn = r'D:\osgeopy-data\Utah\landcover60.tif' 16 | 17 | os.chdir(folder) 18 | 19 | # Read the coordinates and actual classification from the csv. 20 | # This is the training data. 21 | xys = [] 22 | classes = [] 23 | with open(train_fn) as fp: 24 | reader = csv.reader(fp) 25 | next(reader) 26 | for row in reader: 27 | xys.append([float(n) for n in row[:2]]) 28 | classes.append(int(row[2])) 29 | 30 | # Calculate the pixel offsets for the coordinates obtained from 31 | # the csv. 32 | ds = gdal.Open(raster_fns[0]) 33 | pixel_trans = gdal.Transformer(ds, None, []) 34 | offset, ok = pixel_trans.TransformPoints(True, xys) 35 | cols, rows, z = zip(*offset) 36 | 37 | # Get the satellite data. 38 | data = pb.stack_bands(raster_fns) 39 | 40 | # Sample the satellite data at the coordinates from the csv. 41 | sample = data[rows, cols, :] 42 | 43 | # Fit the classification tree. 44 | clf = tree.DecisionTreeClassifier(max_depth=5) 45 | clf = clf.fit(sample, classes) 46 | 47 | # Apply the new classification tree model to the satellite data. 48 | rows, cols, bands = data.shape 49 | data2d = np.reshape(data, (rows * cols, bands)) 50 | prediction = clf.predict(data2d) 51 | prediction = np.reshape(prediction, (rows, cols)) 52 | 53 | # Set the pixels with no valid satellite data to 0. 54 | prediction[np.sum(data, 2) == 0] = 0 55 | 56 | # Save the output. 57 | predict_ds = pb.make_raster(ds, out_fn, prediction, gdal.GDT_Byte, 0) 58 | predict_ds.FlushCache() 59 | levels = pb.compute_overview_levels(predict_ds.GetRasterBand(1)) 60 | predict_ds.BuildOverviews('NEAREST', levels) 61 | 62 | # Apply the color table from the SWReGAP landcover raster. 63 | gap_ds = gdal.Open(gap_fn) 64 | colors = gap_ds.GetRasterBand(1).GetRasterColorTable() 65 | predict_ds.GetRasterBand(1).SetRasterColorTable(colors) 66 | 67 | del ds 68 | -------------------------------------------------------------------------------- /Chapter10/listing10_2.py: -------------------------------------------------------------------------------- 1 | # Script to use ground control points to add a geotransform to a raster. 2 | 3 | import glob 4 | import math 5 | import os 6 | from osgeo import gdal, osr 7 | 8 | # The get_extent function from the text is in ch10funcs.py. 9 | import ch10funcs 10 | 11 | # Don't forget to change the directory. 12 | os.chdir(r'D:\osgeopy-data\Massachusetts') 13 | 14 | # Get the list of tiffs that start with O. 15 | in_files = glob.glob('O*.tif') 16 | 17 | # Loop through all of the files and get the bounding coordinates for the 18 | # whole batch. This will be the output extent. 19 | min_x, max_y, max_x, min_y = ch10funcs.get_extent(in_files[0]) 20 | for fn in in_files[1:]: 21 | minx, maxy, maxx, miny = ch10funcs.get_extent(fn) 22 | min_x = min(min_x, minx) 23 | max_y = max(max_y, maxy) 24 | max_x = max(max_x, maxx) 25 | min_y = min(min_y, miny) 26 | 27 | # Calculate the dimensions for the output based on the output extent. 28 | in_ds = gdal.Open(in_files[0]) 29 | gt = in_ds.GetGeoTransform() 30 | rows = math.ceil((max_y - min_y) / -gt[5]) 31 | columns = math.ceil((max_x - min_x) / gt[1]) 32 | 33 | # Create the output dataset. 34 | driver = gdal.GetDriverByName('gtiff') 35 | out_ds = driver.Create('mosaic.tif', columns, rows) 36 | out_ds.SetProjection(in_ds.GetProjection()) 37 | out_band = out_ds.GetRasterBand(1) 38 | 39 | # Change the upper left coordinates in the geotransform and add it to the 40 | # output image. 41 | gt = list(in_ds.GetGeoTransform()) 42 | gt[0], gt[3] = min_x, max_y 43 | out_ds.SetGeoTransform(gt) 44 | 45 | # Loop through the input files. 46 | for fn in in_files: 47 | in_ds = gdal.Open(fn) 48 | 49 | # Create a transformer between this input image and the output mosaic 50 | # and then use it to calculate the offsets for this raster in the 51 | # mosaic. 52 | trans = gdal.Transformer(in_ds, out_ds, []) 53 | success, xyz = trans.TransformPoint(False, 0, 0) 54 | x, y, z = map(int, xyz) 55 | 56 | # Copy the data. 57 | data = in_ds.GetRasterBand(1).ReadAsArray() 58 | out_band.WriteArray(data, x, y) 59 | 60 | 61 | # From later in the text, get the real-world coordinates from out_ds at 62 | # column 1078 and row 648. 63 | trans = gdal.Transformer(out_ds, None, []) 64 | success, xyz = trans.TransformPoint(0, 1078, 648) 65 | print(xyz) 66 | 67 | del in_ds, out_band, out_ds 68 | -------------------------------------------------------------------------------- /Chapter4/listing4_4.py: -------------------------------------------------------------------------------- 1 | # Script to make a webmap from WFS data. Import listing4-3 so you can reuse 2 | # some of the functions in there. 3 | 4 | import os 5 | from osgeo import ogr 6 | import folium 7 | import listing4_3 8 | 9 | 10 | colors = { 11 | 'action': '#FFFF00', 12 | 'low_threshold': '#734C00', 13 | 'major': '#FF00C5', 14 | 'minor': '#FFAA00', 15 | 'moderate': '#FF0000', 16 | 'no_flooding': '#55FF00', 17 | 'not_defined': '#B2B2B2', 18 | 'obs_not_current': '#B2B2B2', 19 | 'out_of_service': '#4E4E4E' 20 | } 21 | 22 | def get_popup(attributes): 23 | """Return popup text for a feature.""" 24 | template = '''{location}, {waterbody}
25 | {observed} {units}
26 | {status}''' 27 | return template.format(**attributes) 28 | 29 | def add_markers(fmap, json_fn): 30 | ds = ogr.Open(json_fn) 31 | lyr = ds.GetLayer() 32 | for row in lyr: 33 | geom = row.geometry() 34 | color = colors[row.GetField('status')] 35 | fmap.circle_marker([geom.GetY(), geom.GetX()], 36 | line_color=color, 37 | fill_color=color, 38 | radius=5000, 39 | popup=get_popup(row.items())) 40 | 41 | # Because this version of make_map was defined after the version in 42 | # listing4_3 (since it was already imported and evaluated), then this is 43 | # the version that will be used. Unlike in the book text, the first two 44 | # lines of the function here have been changed to reference get_state_geom, 45 | # save_state_gauges, get_bbox, and get_center from the listing4_3 module. 46 | def make_map(state_name, json_fn, html_fn, **kwargs): 47 | """Make a folium map.""" 48 | geom = listing4_3.get_state_geom(state_name) 49 | listing4_3.save_state_gauges(json_fn, listing4_3.get_bbox(geom)) 50 | fmap = folium.Map(location=listing4_3.get_center(geom), **kwargs) 51 | add_markers(fmap, json_fn) 52 | fmap.create_map(path=html_fn) 53 | 54 | 55 | # Top-level code. Don't forget to change the directory. 56 | # Try other options for the tiles parameter. Options are: 57 | # 'OpenStreetMap', 'Mapbox Bright', 'Mapbox Control Room', 58 | # 'Stamen Terrain', 'Stamen Toner' 59 | os.chdir(r'D:\Dropbox\Public\webmaps') 60 | make_map('Oklahoma', 'ok2.json', 'ok2.html', 61 | zoom_start=7, tiles='Stamen Toner') 62 | 63 | -------------------------------------------------------------------------------- /Chapter9/listing9_3.py: -------------------------------------------------------------------------------- 1 | # Script to extract a spatial subset from a raster. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Coordinates for the bounding box to extract. 7 | vashon_ulx, vashon_uly = 532000, 5262600 8 | vashon_lrx, vashon_lry = 548500, 5241500 9 | 10 | # Don't forget to change the directory. 11 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 12 | in_ds = gdal.Open('nat_color.tif') 13 | 14 | # Create an inverse geotransform for the raster. This converts real-world 15 | # coordinates to pixel offsets. 16 | in_gt = in_ds.GetGeoTransform() 17 | inv_gt = gdal.InvGeoTransform(in_gt) 18 | if gdal.VersionInfo()[0] == '1': 19 | if inv_gt[0] == 1: 20 | inv_gt = inv_gt[1] 21 | else: 22 | raise RuntimeError('Inverse geotransform failed') 23 | elif inv_gt is None: 24 | raise RuntimeError('Inverse geotransform failed') 25 | 26 | # Get the offsets that correspond to the bounding box corner coordinates. 27 | offsets_ul = gdal.ApplyGeoTransform( 28 | inv_gt, vashon_ulx, vashon_uly) 29 | offsets_lr = gdal.ApplyGeoTransform( 30 | inv_gt, vashon_lrx, vashon_lry) 31 | 32 | # The offsets are returned as floating point, but we need integers. 33 | off_ulx, off_uly = map(int, offsets_ul) 34 | off_lrx, off_lry = map(int, offsets_lr) 35 | 36 | # Compute the numbers of rows and columns to extract, based on the offsets. 37 | rows = off_lry - off_uly 38 | columns = off_lrx - off_ulx 39 | 40 | # Create an output raster with the correct number of rows and columns. 41 | gtiff_driver = gdal.GetDriverByName('GTiff') 42 | out_ds = gtiff_driver.Create('vashon.tif', columns, rows, 3) 43 | out_ds.SetProjection(in_ds.GetProjection()) 44 | 45 | # Convert the offsets to real-world coordinates for the georeferencing info. 46 | # We can't use the coordinates above because they don't correspond to the 47 | # pixel edges. 48 | subset_ulx, subset_uly = gdal.ApplyGeoTransform( 49 | in_gt, off_ulx, off_uly) 50 | out_gt = list(in_gt) 51 | out_gt[0] = subset_ulx 52 | out_gt[3] = subset_uly 53 | out_ds.SetGeoTransform(out_gt) 54 | 55 | # Loop through the 3 bands. 56 | for i in range(1, 4): 57 | in_band = in_ds.GetRasterBand(i) 58 | out_band = out_ds.GetRasterBand(i) 59 | 60 | # Read the data from the input raster starting at the computed offsets. 61 | data = in_band.ReadAsArray( 62 | off_ulx, off_uly, columns, rows) 63 | 64 | # Write the data to the output, but no offsets are needed because we're 65 | # filling the entire image. 66 | out_band.WriteArray(data) 67 | 68 | del out_ds 69 | -------------------------------------------------------------------------------- /Chapter10/listing10_6.py: -------------------------------------------------------------------------------- 1 | # Script to subset a raster using a VRT. 2 | 3 | import os 4 | from osgeo import gdal 5 | 6 | # Change your directory. 7 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 8 | 9 | # Open the original raster and get its geotransform info. 10 | tmp_ds = gdal.Open('nat_color.tif') 11 | tmp_gt = tmp_ds.GetGeoTransform() 12 | 13 | # Make sure the inverse geotransform worked. Remember that InvGeoTransform 14 | # returns a success flag and the new geotransform in GDAL 1.x but just the 15 | # new geotransform or None in GDAL 2.x. 16 | inv_gt = gdal.InvGeoTransform(tmp_gt) 17 | if gdal.VersionInfo()[0] == '1': 18 | if inv_gt[0] == 1: 19 | inv_gt = inv_gt[1] 20 | else: 21 | raise RuntimeError('Inverse geotransform failed') 22 | elif inv_gt is None: 23 | raise RuntimeError('Inverse geotransform failed') 24 | 25 | # Figure out what the new geotransform is. 26 | vashon_ul = (532000, 5262600) 27 | vashon_lr = (548500, 5241500) 28 | ulx, uly = map(int, gdal.ApplyGeoTransform(inv_gt, *vashon_ul)) 29 | lrx, lry = map(int, gdal.ApplyGeoTransform(inv_gt, *vashon_lr)) 30 | rows = lry - uly 31 | columns = lrx - ulx 32 | gt = list(tmp_gt) 33 | gt[0] += gt[1] * ulx 34 | gt[3] += gt[5] * uly 35 | 36 | # Create the output VRT, which is really just an XML file. 37 | ds = gdal.GetDriverByName('vrt').Create('vashon3.vrt', columns, rows, 3) 38 | ds.SetProjection(tmp_ds.GetProjection()) 39 | ds.SetGeoTransform(gt) 40 | 41 | # The XML definition for each band in the output. 42 | xml = ''' 43 | 44 | {fn} 45 | {band} 46 | 48 | 50 | 51 | ''' 52 | 53 | # The data that will be used to fill the placeholders in the XML. 54 | data = {'fn': 'nat_color.tif', 'band': 1, 55 | 'xoff': ulx, 'yoff': uly, 56 | 'cols': columns, 'rows': rows} 57 | 58 | # Add the first band. 59 | meta = {'source_0': xml.format(**data)} 60 | ds.GetRasterBand(1).SetMetadata(meta, 'vrt_sources') 61 | 62 | # Change the XML so it's pointing to the second band in the full 63 | # dataset and then add band 2 to the output. 64 | data['band'] = 2 65 | meta = {'source_0': xml.format(**data)} 66 | ds.GetRasterBand(2).SetMetadata(meta, 'vrt_sources') 67 | 68 | data['band'] = 3 69 | meta = {'source_0': xml.format(**data)} 70 | ds.GetRasterBand(3).SetMetadata(meta, 'vrt_sources') 71 | 72 | del ds, tmp_ds 73 | -------------------------------------------------------------------------------- /Chapter13/listing13_9.py: -------------------------------------------------------------------------------- 1 | # Script demonstrating how to use multiple rules in a mapnik style. 2 | 3 | ##################### The first part is from listing 13.8. 4 | 5 | import mapnik 6 | 7 | # Create the map object. 8 | srs = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs" 9 | m = mapnik.Map(800, 600, srs) 10 | m.zoom_to_box(mapnik.Box2d(-90.3, 29.7, -89.5, 30.3)) 11 | 12 | # Create a layer from a shapefile. 13 | tiger_fn = r'D:\osgeopy-data\Louisiana\tiger_la_water_CENSUS_2006' 14 | tiger_shp = mapnik.Shapefile(file=tiger_fn) 15 | tiger_lyr = mapnik.Layer('Tiger') 16 | tiger_lyr.datasource = tiger_shp 17 | 18 | # Create a polygon fill symbol. 19 | water_color = mapnik.Color(165, 191, 221) 20 | water_fill_sym = mapnik.PolygonSymbolizer(water_color) 21 | 22 | # Create a symbology style and add it to the layer. 23 | tiger_rule = mapnik.Rule() 24 | tiger_rule.symbols.append(water_fill_sym) 25 | tiger_style = mapnik.Style() 26 | tiger_style.rules.append(tiger_rule) 27 | m.append_style('tiger', tiger_style) 28 | 29 | # Add the style and layer to the map. 30 | tiger_lyr.styles.append('tiger') 31 | 32 | # Comment out this line to get the layers in the right order. 33 | m.layers.append(tiger_lyr) 34 | 35 | 36 | ##################### Here's the new stuff. 37 | 38 | atlas_lyr = mapnik.Layer('National Atlas') 39 | atlas_shp = mapnik.Shapefile(file=r'D:\osgeopy-data\US\wtrbdyp010') 40 | atlas_lyr.datasource = atlas_shp 41 | 42 | # Create a rule for open water. 43 | water_rule = mapnik.Rule() 44 | water_rule.filter = mapnik.Expression( 45 | "[Feature]='Canal' or [Feature]='Lake'") 46 | water_rule.symbols.append(water_fill_sym) 47 | 48 | # Create fill and outline symbolizers for marshes. 49 | marsh_color = mapnik.Color('#66AA66') 50 | marsh_fill_sym = mapnik.PolygonSymbolizer(marsh_color) 51 | marsh_line_sym = mapnik.LineSymbolizer(marsh_color, 2) 52 | 53 | # Create a rule for marshes using the marsh symbols. 54 | marsh_rule = mapnik.Rule() 55 | marsh_rule.filter = mapnik.Expression( 56 | "[Feature]='Swamp or Marsh'") 57 | marsh_rule.symbols.append(marsh_fill_sym) 58 | marsh_rule.symbols.append(marsh_line_sym) 59 | 60 | # Create the atlas style and add the water and marsh rules. 61 | atlas_style = mapnik.Style() 62 | atlas_style.rules.append(water_rule) 63 | atlas_style.rules.append(marsh_rule) 64 | 65 | # Add the style and layer to the map. 66 | m.append_style('atlas', atlas_style) 67 | atlas_lyr.styles.append('atlas') 68 | 69 | # Comment out this line to get the layers in the right order. 70 | m.layers.append(atlas_lyr) 71 | 72 | # # To get the layers in the right order, uncomment this. (See the 73 | # # lines to comment out above). 74 | # m.layers.append(atlas_lyr) 75 | # m.layers.append(tiger_lyr) 76 | 77 | # Save the map. 78 | mapnik.render_to_file(m, r'd:\temp\nola2.png') 79 | -------------------------------------------------------------------------------- /Chapter7/listing7_9.py: -------------------------------------------------------------------------------- 1 | from osgeo import ogr 2 | import ch7funcs 3 | 4 | ds = ogr.Open(r'D:\osgeopy-data\Galapagos', True) 5 | pt_lyr = ds.GetLayerByName('albatross_lambert') 6 | poly_lyr = ds.CreateLayer( 7 | 'albatross_ranges2', pt_lyr.GetSpatialRef(), ogr.wkbPolygon) 8 | id_field = ogr.FieldDefn('tag_id', ogr.OFTString) 9 | area_field = ogr.FieldDefn('area', ogr.OFTReal) #A 10 | area_field.SetWidth(30) #A 11 | area_field.SetPrecision(4) 12 | location_field = ogr.FieldDefn('location', ogr.OFTString) #A 13 | poly_lyr.CreateFields([id_field, area_field, location_field]) 14 | poly_row = ogr.Feature(poly_lyr.GetLayerDefn()) 15 | 16 | 17 | 18 | land_lyr = ds.GetLayerByName('land_lambert') #A 19 | land_row = next(land_lyr) #A 20 | land_poly = land_row.geometry().Buffer(100000) #A 21 | 22 | 23 | 24 | for tag_id in ch7funcs.get_unique(ds, 'albatross_lambert', 'tag_id'): 25 | print('Processing ' + tag_id) 26 | pt_lyr.SetAttributeFilter("tag_id = '{}'".format(tag_id)) 27 | pt_locations = ogr.Geometry(ogr.wkbMultiPoint) 28 | last_location = None 29 | for pt_row in pt_lyr: 30 | pt = pt_row.geometry().Clone() 31 | if not land_poly.Contains(pt): #B 32 | continue #B 33 | if pt.GetX() < -2800000: #C 34 | location = 'island' #C 35 | else: #C 36 | location = 'mainland' #C 37 | if location != last_location: #D 38 | if pt_locations.GetGeometryCount() > 2: 39 | homerange = pt_locations.ConvexHull() 40 | poly_row.SetGeometry(homerange) 41 | poly_row.SetField('tag_id', tag_id) 42 | poly_row.SetField('area', homerange.GetArea()) 43 | poly_row.SetField('location', last_location) 44 | poly_lyr.CreateFeature(poly_row) 45 | pt_locations = ogr.Geometry(ogr.wkbMultiPoint) 46 | last_location = location 47 | pt_locations.AddGeometry(pt) 48 | if pt_locations.GetGeometryCount() > 2: #E 49 | homerange = pt_locations.ConvexHull() 50 | poly_row.SetGeometry(homerange) 51 | poly_row.SetField('tag_id', tag_id) 52 | poly_row.SetField('area', homerange.GetArea()) 53 | poly_row.SetField('location', last_location) 54 | poly_lyr.CreateFeature(poly_row) 55 | -------------------------------------------------------------------------------- /Chapter11/listing11_10.py: -------------------------------------------------------------------------------- 1 | # Script to use proximity analysis and compute mean distance 2 | # from roads. 3 | 4 | import os 5 | import sys 6 | from osgeo import gdal, ogr 7 | 8 | folder = r'D:\osgeopy-data\Idaho' 9 | roads_ln = 'allroads' 10 | wilderness_ln = 'wilderness' 11 | road_raster_fn = 'church_roads.tif' 12 | proximity_fn = 'proximity.tif' 13 | 14 | # Set the cell size for the analysis. 15 | cellsize = 10 16 | 17 | shp_ds = ogr.Open(folder) 18 | 19 | # Get the extent of the wilderness area. 20 | wild_lyr = shp_ds.GetLayerByName(wilderness_ln) 21 | wild_lyr.SetAttributeFilter("WILD_NM = 'Frank Church - RONR'") 22 | envelopes = [row.geometry().GetEnvelope() for row in wild_lyr] 23 | coords = list(zip(*envelopes)) 24 | minx, maxx = min(coords[0]), max(coords[1]) 25 | miny, maxy = min(coords[2]), max(coords[3]) 26 | 27 | # Select the roads that fall within the wilderness extent. 28 | road_lyr = shp_ds.GetLayerByName(roads_ln) 29 | road_lyr.SetSpatialFilterRect(minx, miny, maxx, maxy) 30 | 31 | os.chdir(folder) 32 | tif_driver = gdal.GetDriverByName('GTiff') 33 | 34 | # Figure out the output size. 35 | cols = int((maxx - minx) / cellsize) 36 | rows = int((maxy - miny) / cellsize) 37 | 38 | # Create an empty raster to hold the rasterized roads. 39 | road_ds = tif_driver.Create(road_raster_fn, cols, rows) 40 | road_ds.SetProjection(road_lyr.GetSpatialRef().ExportToWkt()) 41 | road_ds.SetGeoTransform((minx, cellsize, 0, maxy, 0, -cellsize)) 42 | 43 | # Burn the roads into the raster. 44 | gdal.RasterizeLayer( 45 | road_ds, [1], road_lyr, burn_values=[1], 46 | callback=gdal.TermProgress) 47 | 48 | # Burn proximity to roads into a new raster. 49 | prox_ds = tif_driver.Create(proximity_fn, cols, rows, 1, gdal.GDT_Int32) 50 | prox_ds.SetProjection(road_ds.GetProjection()) 51 | prox_ds.SetGeoTransform(road_ds.GetGeoTransform()) 52 | gdal.ComputeProximity( 53 | road_ds.GetRasterBand(1), prox_ds.GetRasterBand(1), 54 | ['DISTUNITS=GEO'], gdal.TermProgress) 55 | 56 | # Burn the wilderness area into a temporary raster. 57 | wild_ds = gdal.GetDriverByName('MEM').Create('tmp', cols, rows) 58 | wild_ds.SetProjection(prox_ds.GetProjection()) 59 | wild_ds.SetGeoTransform(prox_ds.GetGeoTransform()) 60 | gdal.RasterizeLayer( 61 | wild_ds, [1], wild_lyr, burn_values=[1], 62 | callback=gdal.TermProgress) 63 | 64 | # Use the temporary wilderness raster to set the proximity one 65 | # to NoData everywhere that is outside the wilderness area. 66 | wild_data = wild_ds.ReadAsArray() 67 | prox_data = prox_ds.ReadAsArray() 68 | prox_data[wild_data == 0] = -99 69 | prox_ds.GetRasterBand(1).WriteArray(prox_data) 70 | prox_ds.GetRasterBand(1).SetNoDataValue(-99) 71 | prox_ds.FlushCache() 72 | 73 | # Compute statistics and calculate the mean distance to roads, 74 | # which is just the mean value of the proximity raster. 75 | stats = prox_ds.GetRasterBand(1).ComputeStatistics( 76 | False, gdal.TermProgress) 77 | print('Mean distance from roads is', stats[2]) 78 | 79 | del prox_ds, road_ds, shp_ds 80 | -------------------------------------------------------------------------------- /Chapter4/listing4_3.py: -------------------------------------------------------------------------------- 1 | # Script to make a webmap from WFS data. 2 | 3 | import os 4 | import urllib 5 | from osgeo import ogr 6 | import folium 7 | 8 | 9 | def get_bbox(geom): 10 | """Return the bbox based on a geometry envelope.""" 11 | return '{0},{2},{1},{3}'.format(*geom.GetEnvelope()) 12 | 13 | def get_center(geom): 14 | """Return the center point of a geometry.""" 15 | centroid = geom.Centroid() 16 | return [centroid.GetY(), centroid.GetX()] 17 | 18 | def get_state_geom(state_name): 19 | """Return the geometry for a state.""" 20 | ds = ogr.Open(r'D:\osgeopy-data\US\states.geojson') 21 | if ds is None: 22 | raise RuntimeError( 23 | 'Could not open the states dataset. Is the path correct?') 24 | lyr = ds.GetLayer() 25 | lyr.SetAttributeFilter('state = "{0}"'.format(state_name)) 26 | feat = next(lyr) 27 | return feat.geometry().Clone() 28 | 29 | def save_state_gauges(out_fn, bbox=None): 30 | """Save stream gauge data to a geojson file.""" 31 | url = 'http://gis.srh.noaa.gov/arcgis/services/ahps_gauges/' + \ 32 | 'MapServer/WFSServer' 33 | parms = { 34 | 'version': '1.1.0', 35 | 'typeNames': 'ahps_gauges:Observed_River_Stages', 36 | 'srsName': 'urn:ogc:def:crs:EPSG:6.9:4326', 37 | } 38 | if bbox: 39 | parms['bbox'] = bbox 40 | try: 41 | request = 'WFS:{0}?{1}'.format(url, urllib.urlencode(parms)) 42 | except: 43 | request = 'WFS:{0}?{1}'.format(url, urllib.parse.urlencode(parms)) 44 | wfs_ds = ogr.Open(request) 45 | if wfs_ds is None: 46 | raise RuntimeError('Could not open WFS.') 47 | wfs_lyr = wfs_ds.GetLayer(0) 48 | 49 | driver = ogr.GetDriverByName('GeoJSON') 50 | if os.path.exists(out_fn): 51 | driver.DeleteDataSource(out_fn) 52 | json_ds = driver.CreateDataSource(out_fn) 53 | json_ds.CopyLayer(wfs_lyr, '') 54 | 55 | def make_map(state_name, json_fn, html_fn, **kwargs): 56 | """Make a folium map.""" 57 | geom = get_state_geom(state_name) 58 | save_state_gauges(json_fn, get_bbox(geom)) 59 | fmap = folium.Map(location=get_center(geom), **kwargs) 60 | fmap.geo_json(geo_path=json_fn) 61 | fmap.create_map(path=html_fn) 62 | 63 | 64 | # Top-level code. Don't forget to change the directory. The if-statement 65 | # makes it so that the next two lines of code only run if this script is 66 | # being run as a stand-alone script. This way you can import this file 67 | # into listing 4.4 and this part won't run, but you'll have access to all 68 | # of the above functions. 69 | if __name__ == "__main__": 70 | os.chdir(r'D:\Dropbox\Public\webmaps') 71 | make_map('Oklahoma', 'ok.json', 'ok.html', zoom_start=7) 72 | 73 | 74 | # You can look at the capabilities output for this WFS here: 75 | # http://gis.srh.noaa.gov/arcgis/services/ahps_gauges/MapServer/WFSServer?request=GetCapabilities 76 | 77 | # And info for all services from this site here: 78 | # http://gis.srh.noaa.gov/arcgis/rest/services 79 | -------------------------------------------------------------------------------- /Chapter10/listing10_7.py: -------------------------------------------------------------------------------- 1 | # Script to show a custom callback function 2 | 3 | import os 4 | import sys 5 | from osgeo import gdal 6 | 7 | def my_progress(complete, message, progressArg=0.02): 8 | '''Callback function. 9 | 10 | complete - progress percentage between 0 and 1 11 | message - message to show when starting 12 | progressArg - progress increments to print dots, between 0 and 1 13 | ''' 14 | # This runs the first time only, because my_progress will get set here 15 | # so the attribute will always exist after the first time. 16 | if not hasattr(my_progress, 'last_progress'): 17 | sys.stdout.write(message) 18 | my_progress.last_progress = 0 19 | 20 | # Clear out the progress info if we're done. 21 | if complete >= 1: 22 | sys.stdout.write('done\n') 23 | del my_progress.last_progress 24 | 25 | # If not done, show the current progress. 26 | else: 27 | # divmod returns the quotient and remainder of 28 | # complete / progressArg. We're grabbing the quotient. For example, 29 | # if we're halfway done (complete = 0.5) and progressArg = 0.02, 30 | # then progress = 25. If progressArc = 0.25, then progress = 2. 31 | progress = divmod(complete, progressArg)[0] 32 | 33 | # Print dots while the last_progress counter is less than progress. 34 | # Since progress is a bigger number if progressArg is a small 35 | # number, we get more dots the smaller progressArg is. 36 | while my_progress.last_progress < progress: 37 | sys.stdout.write('.') 38 | sys.stdout.flush() 39 | my_progress.last_progress += 1 40 | 41 | 42 | # Try it out when calculating statistics on the natural color Landsat image. 43 | # Change the last parameter (0.05) to other values to see how it affects 44 | # things. 45 | # And don't forget to change folder. 46 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 47 | ds = gdal.Open('nat_color.tif') 48 | for i in range(ds.RasterCount): 49 | ds.GetRasterBand(i + 1).ComputeStatistics(False, my_progress, 0.05) 50 | 51 | 52 | 53 | ############################################################################### 54 | 55 | # Try out the custom progress function when we call it manually. 56 | def process_file(fn): 57 | # Slow things down a bit by counting to 1,000,000 for each file. 58 | for i in range(1000000): 59 | pass # do nothing 60 | 61 | import os 62 | data_dir = r'D:\osgeopy-data' 63 | list_of_files = os.listdir(os.chdir(os.path.join(data_dir, 'Landsat', 'Washington'))) 64 | for i in range(len(list_of_files)): 65 | process_file(list_of_files[i]) 66 | # Uses the default progressArg value. 67 | my_progress(i / float(len(list_of_files)), 'Processing files') 68 | my_progress(100, '') 69 | 70 | # You can also change the progressArg value. 71 | for i in range(len(list_of_files)): 72 | process_file(list_of_files[i]) 73 | my_progress(i / float(len(list_of_files)), 'Processing files', 0.05) 74 | my_progress(100, '') 75 | -------------------------------------------------------------------------------- /Chapter13/listing13_3.py: -------------------------------------------------------------------------------- 1 | # Plot world features as points, lines, and polygons. 2 | 3 | import os 4 | import matplotlib.pyplot as plt 5 | from osgeo import ogr 6 | 7 | # This is the function from listing 11.2. 8 | def plot_polygon(poly, symbol='k-', **kwargs): 9 | """Plots a polygon using the given symbol.""" 10 | for i in range(poly.GetGeometryCount()): 11 | subgeom = poly.GetGeometryRef(i) 12 | x, y = zip(*subgeom.GetPoints()) 13 | plt.plot(x, y, symbol, **kwargs) 14 | 15 | # Use this function to fill polygons (shown shortly after 16 | # this listing in the text). Uncomment this one and comment 17 | # out the one above. 18 | # def plot_polygon(poly, symbol='w', **kwargs): 19 | # """Plots a polygon using the given symbol.""" 20 | # for i in range(poly.GetGeometryCount()): 21 | # x, y = zip(*poly.GetGeometryRef(i).GetPoints()) 22 | # plt.fill(x, y, symbol, **kwargs) 23 | 24 | 25 | # This function is new. 26 | def plot_line(line, symbol='k-', **kwargs): 27 | """Plots a line using the given symbol.""" 28 | x, y = zip(*line.GetPoints()) 29 | plt.plot(x, y, symbol, **kwargs) 30 | 31 | # This function is new. 32 | def plot_point(point, symbol='ko', **kwargs): 33 | """Plots a point using the given symbol.""" 34 | x, y = point.GetX(), point.GetY() 35 | plt.plot(x, y, symbol, **kwargs) 36 | 37 | # More code for lines and points has been added to this function 38 | # from listing 11.2. 39 | def plot_layer(filename, symbol, layer_index=0, **kwargs): 40 | """Plots an OGR layer using the given symbol.""" 41 | ds = ogr.Open(filename) 42 | for row in ds.GetLayer(layer_index): 43 | geom = row.geometry() 44 | geom_type = geom.GetGeometryType() 45 | 46 | # Polygons 47 | if geom_type == ogr.wkbPolygon: 48 | plot_polygon(geom, symbol, **kwargs) 49 | 50 | # Multipolygons 51 | elif geom_type == ogr.wkbMultiPolygon: 52 | for i in range(geom.GetGeometryCount()): 53 | subgeom = geom.GetGeometryRef(i) 54 | plot_polygon(subgeom, symbol, **kwargs) 55 | 56 | # Lines 57 | elif geom_type == ogr.wkbLineString: 58 | plot_line(geom, symbol, **kwargs) 59 | 60 | # Multilines 61 | elif geom_type == ogr.wkbMultiLineString: 62 | for i in range(geom.GetGeometryCount()): 63 | subgeom = geom.GetGeometryRef(i) 64 | plot_line(subgeom, symbol, **kwargs) 65 | 66 | # Points 67 | elif geom_type == ogr.wkbPoint: 68 | plot_point(geom, symbol, **kwargs) 69 | 70 | # Multipoints 71 | elif geom_type == ogr.wkbMultiPoint: 72 | for i in range(geom.GetGeometryCount()): 73 | subgeom = geom.GetGeometryRef(i) 74 | plot_point(subgeom, symbol, **kwargs) 75 | 76 | # Now plot countries, rivers, and cities. 77 | os.chdir(r'D:\osgeopy-data\global') 78 | 79 | plot_layer('ne_110m_admin_0_countries.shp', 'k-') 80 | # If you want to try filling polygons with the second plot_polygon 81 | # function, you'll probably want to change the color for countries. 82 | # plot_layer('ne_110m_admin_0_countries.shp', 'yellow') 83 | 84 | plot_layer('ne_110m_rivers_lake_centerlines.shp', 'b-') 85 | plot_layer('ne_110m_populated_places_simple.shp', 'ro', ms=3) 86 | plt.axis('equal') 87 | plt.gca().get_xaxis().set_ticks([]) 88 | plt.gca().get_yaxis().set_ticks([]) 89 | plt.show() 90 | -------------------------------------------------------------------------------- /Chapter13/listing13_10.py: -------------------------------------------------------------------------------- 1 | # Script to add the roads and city outline to the mapnik map. 2 | 3 | ##################### The first part is from listing 13.9. 4 | 5 | import mapnik 6 | 7 | # Create the map object. 8 | srs = "+proj=longlat +ellps=GRS80 +datum=NAD83 +no_defs" 9 | m = mapnik.Map(800, 600, srs) 10 | m.zoom_to_box(mapnik.Box2d(-90.3, 29.7, -89.5, 30.3)) 11 | 12 | # Create a layer from a shapefile. 13 | tiger_fn = r'D:\osgeopy-data\Louisiana\tiger_la_water_CENSUS_2006' 14 | tiger_shp = mapnik.Shapefile(file=tiger_fn) 15 | tiger_lyr = mapnik.Layer('Tiger') 16 | tiger_lyr.datasource = tiger_shp 17 | 18 | # Create a polygon fill symbol. 19 | water_color = mapnik.Color(165, 191, 221) 20 | water_fill_sym = mapnik.PolygonSymbolizer(water_color) 21 | 22 | # Create a symbology style and add it to the layer. 23 | tiger_rule = mapnik.Rule() 24 | tiger_rule.symbols.append(water_fill_sym) 25 | tiger_style = mapnik.Style() 26 | tiger_style.rules.append(tiger_rule) 27 | m.append_style('tiger', tiger_style) 28 | 29 | # Add the style and layer to the map. 30 | tiger_lyr.styles.append('tiger') 31 | 32 | # Create the atlas layer. 33 | atlas_lyr = mapnik.Layer('National Atlas') 34 | atlas_shp = mapnik.Shapefile(file=r'D:\osgeopy-data\US\wtrbdyp010') 35 | atlas_lyr.datasource = atlas_shp 36 | 37 | # Create a rule for open water. 38 | water_rule = mapnik.Rule() 39 | water_rule.filter = mapnik.Expression( 40 | "[Feature]='Canal' or [Feature]='Lake'") 41 | water_rule.symbols.append(water_fill_sym) 42 | 43 | # Create fill and outline symbolizers for marshes. 44 | marsh_color = mapnik.Color('#66AA66') 45 | marsh_fill_sym = mapnik.PolygonSymbolizer(marsh_color) 46 | marsh_line_sym = mapnik.LineSymbolizer(marsh_color, 2) 47 | 48 | # Create a rule for marshes using the marsh symbols. 49 | marsh_rule = mapnik.Rule() 50 | marsh_rule.filter = mapnik.Expression( 51 | "[Feature]='Swamp or Marsh'") 52 | marsh_rule.symbols.append(marsh_fill_sym) 53 | marsh_rule.symbols.append(marsh_line_sym) 54 | 55 | # Create the atlas style and add the water and marsh rules. 56 | atlas_style = mapnik.Style() 57 | atlas_style.rules.append(water_rule) 58 | atlas_style.rules.append(marsh_rule) 59 | 60 | # Add the style and layer to the map. 61 | m.append_style('atlas', atlas_style) 62 | atlas_lyr.styles.append('atlas') 63 | 64 | 65 | ##################### Here's the new stuff. 66 | 67 | # Specify the road layer's SRS because it has a different one 68 | # than the map. 69 | roads_lyr = mapnik.Layer('Roads', "+init=epsg:4326") 70 | road_shp = mapnik.Shapefile(file=r'D:\osgeopy-data\Louisiana\roads') 71 | roads_lyr.datasource = road_shp 72 | 73 | # Create the roads rules and style. 74 | roads_color = mapnik.Color(170, 170, 127) 75 | 76 | roads_primary_rule = mapnik.Rule() 77 | roads_primary_rule.filter = mapnik.Expression("[fclass]='primary'") 78 | roads_primary_sym = mapnik.LineSymbolizer(roads_color, 1.5) 79 | roads_primary_rule.symbols.append(roads_primary_sym) 80 | 81 | roads_secondary_rule = mapnik.Rule() 82 | roads_secondary_rule.filter = mapnik.Expression( 83 | "[fclass]='secondary' or [fclass]='tertiary'") 84 | roads_secondary_sym = mapnik.LineSymbolizer(roads_color, 0.5) 85 | roads_secondary_rule.symbols.append(roads_secondary_sym) 86 | 87 | roads_style = mapnik.Style() 88 | roads_style.rules.append(roads_primary_rule) 89 | roads_style.rules.append(roads_secondary_rule) 90 | 91 | # Add the roads style to the map and roads layer. 92 | m.append_style('roads style', roads_style) 93 | roads_lyr.styles.append('roads style') 94 | 95 | # Create the city layer. 96 | city_lyr = mapnik.Layer('City Outline') 97 | city_shp = mapnik.Shapefile(file=r'D:\osgeopy-data\Louisiana\NOLA') 98 | city_lyr.datasource = city_shp 99 | 100 | # Create a black dashed line. 101 | city_color = mapnik.Color('black') 102 | city_sym = mapnik.LineSymbolizer(city_color, 2) 103 | city_sym.stroke.add_dash(4, 2) 104 | city_rule = mapnik.Rule() 105 | city_rule.symbols.append(city_sym) 106 | city_style = mapnik.Style() 107 | city_style.rules.append(city_rule) 108 | 109 | m.append_style('city style', city_style) 110 | city_lyr.styles.append('city style') 111 | 112 | # Add all of the layers to the map. 113 | m.layers.append(atlas_lyr) 114 | m.layers.append(tiger_lyr) 115 | m.layers.append(roads_lyr) 116 | m.layers.append(city_lyr) 117 | 118 | # Save the map. 119 | mapnik.render_to_file(m, r'd:\temp\nola3.png') 120 | 121 | # This saves an xml file describing the map. It's used in a later 122 | # example in the book. 123 | mapnik.save_map(m, r'd:\temp\nola_map.xml') 124 | -------------------------------------------------------------------------------- /Chapter4/chapter4.py: -------------------------------------------------------------------------------- 1 | import os 2 | from osgeo import ogr 3 | import ospybook as pb 4 | from ospybook.vectorplotter import VectorPlotter 5 | 6 | 7 | # Set this variable to your osgeopy-data directory so that the following 8 | # examples will work without editing. We'll use the os.path.join() function 9 | # to combine this directory and the filenames to make a complete path. Of 10 | # course, you can type the full path to the file for each example if you'd 11 | # prefer. 12 | data_dir = r'D:\osgeopy-data' 13 | # data_dir = 14 | 15 | 16 | 17 | ######################### 4.2 More data formats ############################# 18 | 19 | # A function to print out the layers in a data source. 20 | def print_layers(fn): 21 | ds = ogr.Open(fn, 0) 22 | if ds is None: 23 | raise OSError('Could not open {}'.format(fn)) 24 | for i in range(ds.GetLayerCount()): 25 | lyr = ds.GetLayer(i) 26 | print('{0}: {1}'.format(i, lyr.GetName())) 27 | 28 | # Try out the function. 29 | print_layers(os.path.join(data_dir, 'Washington', 'large_cities.geojson')) 30 | 31 | 32 | ######################### 4.2.1 SpatiaLite ################################## 33 | 34 | # Use the function in ospybook to look at a SpatiaLite database. 35 | pb.print_layers(os.path.join(data_dir, 'global', 'natural_earth_50m.sqlite')) 36 | 37 | # Get the populated_places layer. 38 | ds = ogr.Open(os.path.join(data_dir, 'global', 'natural_earth_50m.sqlite')) 39 | lyr = ds.GetLayer('populated_places') 40 | 41 | # Plot the populated places layer in the SpatiaList database interactively. 42 | vp = VectorPlotter(True) 43 | vp.plot(lyr, 'bo') 44 | 45 | # Plot the populated places layer in the SpatiaList database non-interactively. 46 | vp = VectorPlotter(False) 47 | vp.plot(lyr, 'bo') 48 | vp.draw() 49 | 50 | 51 | ######################### 4.2.2 PostGIS ##################################### 52 | 53 | # Print out layers in a PostGIS database. This will not work for you unless 54 | # you set up a PostGIS server. 55 | pb.print_layers('PG:user=chris password=mypass dbname=geodata') 56 | 57 | 58 | ######################### 4.2.3 Folders ##################################### 59 | 60 | # Get the shapefile layers in a folder. 61 | pb.print_layers(os.path.join(data_dir, 'US')) 62 | 63 | # EXTRA 64 | # Get the csv layers in a folder. 65 | pb.print_layers(os.path.join(data_dir, 'US', 'csv')) 66 | 67 | # Use csv or shapefiles as data sources. 68 | pb.print_layers(os.path.join(data_dir, 'US', 'volcanoes.csv')) 69 | pb.print_layers(os.path.join(data_dir, 'US', 'citiesx020.shp')) 70 | 71 | # Just for the fun of it, try to force the CSV driver to use a folder with 72 | # other stuff in it. The folder will not be opened successfully and ds will be 73 | # None. 74 | driver = ogr.GetDriverByName('CSV') 75 | ds = driver.Open(os.path.join(data_dir, 'US')) 76 | print(ds) 77 | 78 | 79 | ######################### 4.2.4 Esri file geodatabase ####################### 80 | 81 | # Print out layers in an Esri file geodatabase. 82 | fn = os.path.join(data_dir, 'global', 'natural_earth.gdb') 83 | pb.print_layers(fn) 84 | 85 | # Get a layer inside a feature dataset. 86 | ds = ogr.Open(fn) 87 | lyr = ds.GetLayer('countries_10m') 88 | 89 | # Print out some attributes to make sure it worked. 90 | pb.print_attributes(lyr, 5, ['NAME', 'POP_EST']) 91 | 92 | # Export a feature class from geodatabase to a shapefile. 93 | out_folder = os.path.join(data_dir, 'global') 94 | gdb_ds = ogr.Open(fn) 95 | gdb_lyr = gdb_ds.GetLayerByName('countries_110m') 96 | shp_ds = ogr.Open(out_folder, 1) 97 | shp_ds.CopyLayer(gdb_lyr, 'countries_110m') 98 | del shp_ds, gdb_ds 99 | 100 | # Use listing 4.2 to copy shapefiles in a folder into a file geodatabase. 101 | import listing4_2 102 | shp_folder = os.path.join(data_dir, 'global') 103 | gdb_fn = os.path.join(shp_folder, 'osgeopy-data.gdb') 104 | listing4_2.layers_to_feature_dataset(shp_folder, gdb_fn, 'global') 105 | 106 | 107 | ######################### 4.2.5 Web Feature Service ######################### 108 | 109 | # Print out layers in a WFS. 110 | url = 'WFS:http://gis.srh.noaa.gov/arcgis/services/watchWarn/MapServer/WFSServer' 111 | pb.print_layers(url) 112 | 113 | # Get the first warning from the WFS. This might take a while because it has 114 | # to download all of the data first. 115 | ds = ogr.Open(url) 116 | lyr = ds.GetLayer(1) 117 | print(lyr.GetFeatureCount()) 118 | feat = lyr.GetNextFeature() 119 | print(feat.GetField('prod_type')) 120 | 121 | # Get the first warning from the WFS by limiting the returned features to 1. 122 | ds = ogr.Open(url + '?MAXFEATURES=1') 123 | lyr = ds.GetLayer(1) 124 | print(lyr.GetFeatureCount()) 125 | 126 | # Extra: Plot the WatchesWarnings layer (this will probably take a while). 127 | vp = VectorPlotter(False) 128 | ds = ogr.Open(os.path.join(data_dir, 'US', 'states_48.shp')) 129 | lyr = ds.GetLayer(0) 130 | vp.plot(lyr, fill=False) 131 | ds = ogr.Open(url) 132 | lyr = ds.GetLayer('watchWarn:WatchesWarnings') 133 | vp.plot(lyr) 134 | vp.draw() 135 | 136 | 137 | 138 | ######################## 4.3 Testing capabilities ########################### 139 | 140 | # Test if you can create a new shapefile in a folder opened for reading only. 141 | dirname = os.path.join(data_dir, 'global') 142 | ds = ogr.Open(dirname) 143 | print(ds.TestCapability(ogr.ODsCCreateLayer)) 144 | 145 | # Test if you can create a new shapefile in a folder opened for writing. 146 | ds = ogr.Open(dirname, 1) 147 | print(ds.TestCapability(ogr.ODsCCreateLayer)) 148 | 149 | 150 | # Make a copy of a shapefile for the following examples. 151 | original_fn = os.path.join(data_dir, 'Washington', 'large_cities2.shp') 152 | new_fn = os.path.join(data_dir, 'output', 'large_cities3.shp') 153 | pb.copy_datasource(original_fn, new_fn) 154 | 155 | # Try opening the datasource read-only and see if you can add a field (you 156 | # can't). The example in the book opens it for writing, as in the next 157 | # snippet in this file). 158 | ds = ogr.Open(new_fn, 0) 159 | if ds is None: 160 | sys.exit('Could not open {0}.'.format(new_fn)) 161 | lyr = ds.GetLayer(0) 162 | 163 | if not lyr.TestCapability(ogr.OLCCreateField): 164 | raise RuntimeError('Cannot create fields.') 165 | lyr.CreateField(ogr.FieldDefn('ID', ogr.OFTInteger)) 166 | 167 | # Now try it with the datasource opened for writing. This is the way the 168 | # book does it, because this is how it should be done. 169 | ds = ogr.Open(new_fn, 1) 170 | if ds is None: 171 | sys.exit('Could not open {0}.'.format(new_fn)) 172 | lyr = ds.GetLayer(0) 173 | 174 | if not lyr.TestCapability(ogr.OLCCreateField): 175 | raise RuntimeError('Cannot create fields.') 176 | lyr.CreateField(ogr.FieldDefn('ID', ogr.OFTInteger)) 177 | 178 | # Use the ospybook print_capabilities function. 179 | driver = ogr.GetDriverByName('ESRI Shapefile') 180 | pb.print_capabilities(driver) 181 | -------------------------------------------------------------------------------- /Chapter8/chapter8.py: -------------------------------------------------------------------------------- 1 | import os 2 | from osgeo import ogr 3 | import ospybook as pb 4 | from ospybook.vectorplotter import VectorPlotter 5 | 6 | 7 | # Set this variable to your osgeopy-data directory so that the following 8 | # examples will work without editing. We'll use the os.path.join() function 9 | # to combine this directory and the filenames to make a complete path. Of 10 | # course, you can type the full path to the file for each example if you'd 11 | # prefer. 12 | data_dir = r'D:\osgeopy-data' 13 | # data_dir = 14 | 15 | 16 | 17 | ################### 8.2 Using spatial references with OSR ################### 18 | 19 | # import osr so you can work with spatial references. 20 | from osgeo import osr 21 | 22 | 23 | ###################### 8.2.1 Spatial reference objects ###################### 24 | 25 | # Look at a geographic SRS. 26 | ds = ogr.Open(os.path.join(data_dir, 'US', 'states_48.shp')) 27 | srs = ds.GetLayer().GetSpatialRef() 28 | 29 | # Well Known Text (WKT) 30 | print(srs) 31 | 32 | # PROJ.4 33 | print(srs.ExportToProj4()) 34 | 35 | # XML 36 | print(srs.ExportToXML()) 37 | 38 | # Look at a UTM SRS. 39 | utm_sr = osr.SpatialReference() 40 | utm_sr.ImportFromEPSG(26912) 41 | print(utm_sr) # WKT 42 | print(utm_sr.ExportToProj4()) # PROJ.4 43 | print(utm_sr.ExportToXML()) # XML 44 | 45 | # Get the projection name. 46 | print(utm_sr.GetAttrValue('PROJCS')) 47 | 48 | # Get the authority. 49 | print(utm_sr.GetAttrValue('AUTHORITY')) 50 | print(utm_sr.GetAttrValue('AUTHORITY', 1)) 51 | 52 | # Get the datum code. 53 | print(utm_sr.GetAuthorityCode('DATUM')) 54 | 55 | # Get the false easting. 56 | print(utm_sr.GetProjParm(osr.SRS_PP_FALSE_EASTING)) 57 | 58 | 59 | ################## 8.2.2 Creating spatial reference objects ################# 60 | 61 | # Create a UTM SRS from an EPSG code. 62 | sr = osr.SpatialReference() 63 | sr.ImportFromEPSG(26912) 64 | print(sr.GetAttrValue('PROJCS')) 65 | 66 | # Create a UTM SRS from a PROJ.4 string. 67 | sr = osr.SpatialReference() 68 | sr.ImportFromProj4('''+proj=utm +zone=12 +ellps=GRS80 69 | +towgs84=0,0,0,0,0,0,0 +units=m +no_defs ''') 70 | print(sr.GetAttrValue('PROJCS')) 71 | 72 | # Create a unprojected SRS from a WKT string. 73 | wkt = '''GEOGCS["GCS_North_American_1983", 74 | DATUM["North_American_Datum_1983", 75 | SPHEROID["GRS_1980",6378137.0,298.257222101]], 76 | PRIMEM["Greenwich",0.0], 77 | UNIT["Degree",0.0174532925199433]]''' 78 | sr = osr.SpatialReference(wkt) 79 | print(sr) 80 | 81 | # Create an Albers SRS using parameters. 82 | sr = osr.SpatialReference() 83 | sr.SetProjCS('USGS Albers') 84 | sr.SetWellKnownGeogCS('NAD83') 85 | sr.SetACEA(29.5, 45.5, 23, -96, 0, 0) 86 | sr.Fixup() 87 | sr.Validate() 88 | print(sr) 89 | 90 | 91 | ######################## 8.2.3 Assigning a SRS to data ###################### 92 | 93 | # Make sure that the output folder exists in your data directory before 94 | # trying this example. 95 | out_fn = os.path.join(data_dir, 'output', 'testdata.shp') 96 | 97 | # Create an empty shapefile that uses a UTM SRS. If you run this it will 98 | # create the shapefile with a .prj file containing the SRS info. 99 | sr = osr.SpatialReference() 100 | sr.ImportFromEPSG(26912) 101 | ds = ogr.GetDriverByName('ESRI Shapefile').CreateDataSource(out_fn) 102 | lyr = ds.CreateLayer('counties', sr, ogr.wkbPolygon) 103 | 104 | 105 | ######################### 8.2.4 Projecting geometries ####################### 106 | 107 | # Get the world landmasses and plot them. 108 | world = pb.get_shp_geom(os.path.join(data_dir, 'global', 'ne_110m_land_1p.shp')) 109 | vp = VectorPlotter(True) 110 | vp.plot(world) 111 | 112 | # Create a point for the Eiffel Tower. 113 | tower = ogr.Geometry(wkt='POINT (2.294694 48.858093)') 114 | tower.AssignSpatialReference(osr.SpatialReference(osr.SRS_WKT_WGS84)) 115 | 116 | # Try to reproject the world polygon to Web Mercator. This should spit out 117 | # an error. 118 | web_mercator_sr = osr.SpatialReference() 119 | web_mercator_sr.ImportFromEPSG(3857) 120 | world.TransformTo(web_mercator_sr) 121 | 122 | # Set a config variable and try the projection again. 123 | from osgeo import gdal 124 | gdal.SetConfigOption('OGR_ENABLE_PARTIAL_REPROJECTION', 'TRUE') 125 | web_mercator_sr = osr.SpatialReference() 126 | web_mercator_sr.ImportFromEPSG(3857) 127 | world.TransformTo(web_mercator_sr) 128 | tower.TransformTo(web_mercator_sr) 129 | print(tower) 130 | vp.clear() 131 | vp.plot(world) 132 | 133 | # Create a coordinate transformation between Web Mercator and Gall-Peters. 134 | peters_sr = osr.SpatialReference() 135 | peters_sr.ImportFromProj4("""+proj=cea +lon_0=0 +x_0=0 +y_0=0 136 | +lat_ts=45 +ellps=WGS84 +datum=WGS84 137 | +units=m +no_defs""") 138 | ct = osr.CoordinateTransformation(web_mercator_sr, peters_sr) 139 | world.Transform(ct) 140 | vp.clear() 141 | vp.plot(world) 142 | 143 | # Create an unprojected NAD27 SRS and add datum shift info. 144 | sr = osr.SpatialReference() 145 | sr.SetWellKnownGeogCS('NAD27') 146 | sr.SetTOWGS84(-8, 160, 176) 147 | print(sr) 148 | 149 | 150 | 151 | ################################# 8.3 pyproj ################################ 152 | 153 | ####################### 8.3.1 Transforming between SRS ###################### 154 | 155 | # Transform lat/lon to UTM. 156 | import pyproj 157 | utm_proj = pyproj.Proj('+proj=utm +zone=31 +ellps=WGS84') 158 | x, y = utm_proj(2.294694, 48.858093) 159 | print(x, y) 160 | 161 | # Go back to lat/lon. 162 | x1, y1 = utm_proj(x, y, inverse=True) 163 | print(x1, y1) 164 | 165 | # Convert UTM WGS84 coordinates to UTM NAD27. 166 | wgs84 = pyproj.Proj('+proj=utm +zone=18 +datum=WGS84') 167 | nad27 = pyproj.Proj('+proj=utm +zone=18 +datum=NAD27') 168 | x, y = pyproj.transform(wgs84, nad27, 580744.32, 4504695.26) 169 | print(x, y) 170 | 171 | 172 | ####################### 8.3.2 Great-circle calculations ##################### 173 | 174 | # Set lat/lon coordinates for Los Angeles and Berlin. 175 | la_lat, la_lon = 34.0500, -118.2500 176 | berlin_lat, berlin_lon = 52.5167, 13.3833 177 | 178 | # Create a WGS84 Geod. 179 | geod = pyproj.Geod(ellps='WGS84') 180 | 181 | # Get the bearings and distance between LA and Berlin 182 | forward, back, dist = geod.inv(la_lon, la_lat, berlin_lon, berlin_lat) 183 | print('forward: {}\nback: {}\ndist: {}'.format(forward, back, dist)) 184 | 185 | # Get your final coordinates if you start in Berlin and go dist distance in 186 | # the back direction. These coordinates should match LA. 187 | x, y, bearing = geod.fwd(berlin_lon, berlin_lat, back, dist) 188 | print('{}, {}\n{}'.format(x, y, bearing)) 189 | 190 | # Get a list of equally spaced coordinates along the great circel from LA 191 | # to Berlin. 192 | coords = geod.npts(la_lon, la_lat, berlin_lon, berlin_lat, 100) 193 | 194 | # Only print the first 3. 195 | for i in range(3): 196 | print(coords[i]) 197 | -------------------------------------------------------------------------------- /ospybook/ospybook/vectorplotter.py: -------------------------------------------------------------------------------- 1 | from osgeo import ogr 2 | 3 | import ospybook as pb 4 | from ospybook.simplevectorplotter import SimpleVectorPlotter 5 | 6 | point_types = [ogr.wkbPoint, ogr.wkbPoint25D, 7 | ogr.wkbMultiPoint, ogr.wkbMultiPoint25D] 8 | line_types = [ogr.wkbLineString, ogr.wkbLineString25D, 9 | ogr.wkbMultiLineString, ogr.wkbMultiLineString25D] 10 | polygon_types = [ogr.wkbPolygon, ogr.wkbPolygon25D, 11 | ogr.wkbMultiPolygon, ogr.wkbMultiPolygon25D] 12 | 13 | class VectorPlotter(SimpleVectorPlotter): 14 | """Plots vector data represented as OGR layers and geometries.""" 15 | 16 | def __init__(self, interactive, ticks=False, figsize=None, limits=None): 17 | """Construct a new VectorPlotter. 18 | 19 | interactive - boolean flag denoting interactive mode 20 | ticks - boolean flag denoting whether to show axis tickmarks 21 | figsize - optional figure size 22 | limits - optional geographic limits (x_min, x_max, y_min, y_max) 23 | """ 24 | super(VectorPlotter, self).__init__(interactive, ticks, figsize, limits) 25 | 26 | def plot(self, geom_or_lyr, symbol='', name='', **kwargs): 27 | """Plot a geometry or layer. 28 | geom_or_lyr - geometry, layer, or filename 29 | symbol - optional pyplot symbol to draw the geometries with 30 | name - optional name to assign to layer so can access it later 31 | kwargs - optional pyplot drawing parameters 32 | """ 33 | if type(geom_or_lyr) is str: 34 | lyr, ds = pb._get_layer(geom_or_lyr) 35 | self.plot_layer(lyr, symbol, name, **kwargs) 36 | elif type(geom_or_lyr) is ogr.Geometry: 37 | self.plot_geom(geom_or_lyr, symbol, name, **kwargs) 38 | elif type(geom_or_lyr) is ogr.Layer: 39 | self.plot_layer(geom_or_lyr, symbol, name, **kwargs) 40 | else: 41 | raise RuntimeError('{} is not supported.'.format(type(geom_or_lyr))) 42 | 43 | def plot_geom(self, geom, symbol='', name='', **kwargs): 44 | """Plot a geometry. 45 | geom - geometry 46 | symbol - optional pyplot symbol to draw the geometry with 47 | name - optional name to assign to layer so can access it later 48 | kwargs - optional pyplot drawing parameters 49 | """ 50 | geom_type = geom.GetGeometryType() 51 | if not symbol: 52 | if geom_type in point_types: 53 | symbol = self._point_symbol() 54 | elif geom_type in line_types: 55 | symbol = self._line_symbol() 56 | if geom_type in polygon_types and not self._kwargs_has_color(**kwargs): 57 | kwargs['fc'] = symbol or self._next_color() 58 | graphics = self._plot_geom(geom, symbol, **kwargs) 59 | self._set_graphics(graphics, name, symbol or kwargs) 60 | 61 | def plot_layer(self, lyr, symbol='', name='', **kwargs): 62 | """Plot a layer. 63 | geom - layer 64 | symbol - optional pyplot symbol to draw the geometries with 65 | name - optional name to assign to layer so can access it later 66 | kwargs - optional pyplot drawing parameters 67 | """ 68 | geom_type = lyr.GetLayerDefn().GetGeomType() 69 | if geom_type == ogr.wkbUnknown: 70 | feat = lyr.GetFeature(0) 71 | geom_type = feat.geometry().GetGeometryType() 72 | if not symbol: 73 | if geom_type in point_types: 74 | symbol = self._point_symbol() 75 | elif geom_type in line_types: 76 | symbol = self._line_symbol() 77 | if geom_type in polygon_types and not self._kwargs_has_color(**kwargs): 78 | kwargs['fc'] = symbol or self._next_color() 79 | lyr.ResetReading() 80 | graphics = [] 81 | for feat in lyr: 82 | graphics += self._plot_geom(feat.geometry(), symbol, **kwargs) 83 | self._set_graphics(graphics, name, symbol or kwargs) 84 | lyr.ResetReading() 85 | 86 | def _plot_geom(self, geom, symbol='', **kwargs): 87 | """Plot a geometry.""" 88 | geom_name = geom.GetGeometryName() 89 | if geom_name == 'POINT': 90 | symbol = symbol or self._point_symbol() 91 | return self._plot_point(self._get_point_coords(geom), symbol, **kwargs) 92 | elif geom_name == 'MULTIPOINT': 93 | symbol = symbol or self._point_symbol() 94 | return self._plot_multipoint(self._get_multipoint_coords(geom), symbol, **kwargs) 95 | elif geom_name == 'LINESTRING': 96 | return self._plot_line(self._get_line_coords(geom), symbol, **kwargs) 97 | elif geom_name == 'MULTILINESTRING': 98 | return self._plot_multiline(self._get_multiline_coords(geom), symbol, **kwargs) 99 | elif geom_name == 'POLYGON': 100 | return self._plot_polygon(self._get_polygon_coords(geom), **kwargs) 101 | elif geom_name == 'MULTIPOLYGON': 102 | return self._plot_multipolygon(self._get_multipolygon_coords(geom), **kwargs) 103 | elif geom_name == 'GEOMETRYCOLLECTION': 104 | graphics = [] 105 | for i in range(geom.GetGeometryCount()): 106 | graphics += self._plot_geom(geom.GetGeometryRef(i), symbol, **kwargs) 107 | return graphics 108 | else: 109 | raise RuntimeError('{} not supported'.format(geom_name)) 110 | 111 | def _get_line_coords(self, geom): 112 | """Get line coordinates as a list of (x, y) tuples.""" 113 | return [coords[:2] for coords in geom.GetPoints()] 114 | 115 | def _get_point_coords(self, geom): 116 | """Get point coordinates as an (x, y) tuple.""" 117 | return (geom.GetX(), geom.GetY()) 118 | 119 | def _get_polygon_coords(self, geom): 120 | """Get polygon coordinates as a list of lists of (x, y) tuples.""" 121 | coords = [] 122 | for i in range(geom.GetGeometryCount()): 123 | coords.append(self._get_line_coords(geom.GetGeometryRef(i))) 124 | return coords 125 | 126 | def _get_multiline_coords(self, geom): 127 | """Get multiline coordinates as a list of lists of (x, y) tuples.""" 128 | coords = [] 129 | for i in range(geom.GetGeometryCount()): 130 | coords.append(self._get_line_coords(geom.GetGeometryRef(i))) 131 | return coords 132 | 133 | def _get_multipoint_coords(self, geom): 134 | """Get multipoint coordinates as a list of (x, y) tuples.""" 135 | coords = [] 136 | for i in range(geom.GetGeometryCount()): 137 | coords.append(self._get_point_coords(geom.GetGeometryRef(i))) 138 | return coords 139 | 140 | def _get_multipolygon_coords(self, geom): 141 | """Get multipolygon coordinates as a list of lists rings.""" 142 | coords = [] 143 | for i in range(geom.GetGeometryCount()): 144 | coords.append(self._get_polygon_coords(geom.GetGeometryRef(i))) 145 | return coords 146 | 147 | def _kwargs_has_color(self, **kwargs): 148 | """Check if kwargs dictionary has a facecolor entry.""" 149 | return 'fc' in kwargs or 'facecolor' in kwargs 150 | -------------------------------------------------------------------------------- /Chapter11/chapter11.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import numpy as np 4 | from osgeo import gdal 5 | import scipy.stats 6 | import ospybook as pb 7 | 8 | # Set this variable to your osgeopy-data directory so that the following 9 | # examples will work without editing. We'll use the os.path.join() function 10 | # to combine this directory and the filenames to make a complete path. Of 11 | # course, you can type the full path to the file for each example if you'd 12 | # prefer. 13 | data_dir = r'D:\osgeopy-data' 14 | # data_dir = 15 | 16 | 17 | 18 | ############################## 11.1 Intro to NumPy ########################## 19 | 20 | # Test out an array. 21 | import numpy as np 22 | a = np.arange(12) 23 | print(a) 24 | print(a[1]) 25 | print(a[1:5]) 26 | 27 | # Change an array's shape. 28 | a = np.reshape(a, (3,4)) 29 | print(a) 30 | print(a[1,2]) 31 | 32 | # Access entire rows or columns. 33 | print(a[1]) 34 | print(a[:,2]) 35 | 36 | # Access 2-dimensional slice. 37 | print(a[1:,1:3]) 38 | print(a[2,:-1]) 39 | 40 | # Math 41 | a = np.array([[1, 3, 4], [2, 7, 6]]) 42 | b = np.array([[5, 2, 9], [3, 6, 4]]) 43 | print(a) 44 | print(b) 45 | print(a + b) 46 | print(a > b) 47 | 48 | # Where 49 | print(np.where(a > b, 10, 5)) 50 | print(np.where(a > b, a, b)) 51 | 52 | # Access non-contiguous data. 53 | a = np.random.randint(0, 20, 12) 54 | print(a) 55 | print(a[[8, 0, 3]]) 56 | 57 | a = np.reshape(a, (3, 4)) 58 | print(a) 59 | print(a[[2, 0, 0], [0, 0, 3]]) 60 | 61 | # Use Booleans. 62 | b = np.reshape(np.random.randint(0, 20, 12), (3, 4)) > 10 63 | print(b) 64 | print(a[b]) 65 | print(np.mean(a[a>5])) 66 | 67 | # Create arrays. 68 | print(np.zeros((3,2))) 69 | print(np.ones((2,3), np.int)) 70 | print(np.ones((2,3), np.int) * 5) 71 | print(np.empty((2,2))) 72 | 73 | 74 | ############################## 11.2 Map Algebra ############################# 75 | 76 | ########################### 11.2.1 Local Analyses ########################### 77 | 78 | # These examples are here because they're in the text, but really you should 79 | # follow the method shown in listing 10.2. 80 | 81 | os.chdir(os.path.join(data_dir, 'Massachusetts')) 82 | in_fn = 'm_4207162_ne_19_1_20140718_20140923_clip.tif' 83 | out_fn = 'ndvi2.tif' 84 | 85 | ds = gdal.Open(in_fn) 86 | red = ds.GetRasterBand(1).ReadAsArray().astype(np.float) 87 | nir = ds.GetRasterBand(4).ReadAsArray() 88 | red = np.ma.masked_where(nir + red == 0, red) 89 | 90 | # This is the first method shown in the text. 91 | ndvi = (nir - red) / (nir + red) 92 | ndvi = np.where(np.isnan(ndvi), -99, ndvi) 93 | ndvi = np.where(np.isinf(ndvi), -99, ndvi) 94 | pb.make_raster(ds, 'ndvi2.tif', ndvi, gdal.GDT_Float32, -99) 95 | 96 | # This is the second method shown in the text. 97 | ndvi = np.where(nir + red > 0, (nir - red) / (nir + red), -99) 98 | pb.make_raster(ds, 'ndvi3.tif', ndvi, gdal.GDT_Float32, -99) 99 | 100 | del ds 101 | 102 | 103 | ########################### 11.2.2 Focal Analyses ########################### 104 | 105 | indata = np.array([ 106 | [3, 5, 6, 4, 4, 3], 107 | [4, 5, 8, 9, 6, 5], 108 | [2, 2, 5, 7, 6, 4], 109 | [5, 7, 9, 8, 9, 7], 110 | [4, 6, 5, 7, 7, 5], 111 | [3, 2, 5, 3, 4, 4]]) 112 | 113 | outdata = np.zeros((6, 6)) 114 | 115 | outdata[2,2] = (indata[1,1] + indata[1,2] + indata[1,3] + 116 | indata[2,1] + indata[2,2] + indata[2,3] + 117 | indata[3,1] + indata[3,2] + indata[3,3]) / 9 118 | print(outdata) 119 | 120 | # DO NOT try this on a real image because it's way too slow. 121 | rows, cols = indata.shape 122 | outdata = np.zeros(indata.shape, np.float32) 123 | for i in range(1, rows-1): 124 | for j in range(1, cols-1): 125 | outdata[i,j] = np.mean(indata[i-1:i+2, j-1:j+2]) 126 | print(outdata) 127 | 128 | # This one is fine, but is a pain to type out. 129 | outdata = np.zeros(indata.shape, np.float32) 130 | outdata[1:rows-1, 1:cols-1] = ( 131 | indata[0:-2, 0:-2] + indata[0:-2, 1:-1] + indata[0:-2, 2:] + 132 | indata[1:-1, 0:-2] + indata[1:-1, 1:-1] + indata[1:-1, 2:] + 133 | indata[2: , 0:-2] + indata[2: , 1:-1] + indata[2: , 2:]) / 9 134 | print(outdata) 135 | 136 | # Check out some slices 137 | slices = [] 138 | for i in range(3): 139 | for j in range(3): 140 | slices.append(indata[i:rows-2+i, j:cols-2+j]) 141 | print(slices) 142 | 143 | # This is the upper left slice. 144 | print(slices[0]) 145 | 146 | # Stack the slices and compute the mean. 147 | stacked = np.dstack(slices) 148 | outdata = np.zeros(indata.shape, np.float32) 149 | outdata[1:-1, 1:-1] = np.mean(stacked, 2) 150 | print(outdata) 151 | 152 | 153 | ########################### 11.2.3 Zonal Analyses ########################### 154 | 155 | # Function to get histogram bins. 156 | def get_bins(data): 157 | """Return bin edges for all unique values in data. """ 158 | bins = np.unique(data) 159 | return np.append(bins[~np.isnan(bins)], max(bins) + 1) 160 | 161 | # Load the data from the figure. 162 | os.chdir(os.path.join(data_dir, 'misc')) 163 | landcover = gdal.Open('grid102.tif').ReadAsArray() 164 | zones = gdal.Open('grid101.tif').ReadAsArray() 165 | 166 | # Calculate the 2-way histogram. 167 | hist, zone_bins, landcover_bins = np.histogram2d( 168 | zones.flatten(), landcover.flatten(), 169 | [get_bins(zones), get_bins(landcover)]) 170 | print(hist) 171 | 172 | 173 | # Example to create a one-dimensional bin instead of the 2-d one in 174 | # listing 11.9. 175 | 176 | data_dir = r'D:\osgeopy-data' 177 | 178 | # Read in the ecoregion data and get appropriate bins. 179 | os.chdir(os.path.join(data_dir, 'Utah')) 180 | 181 | eco_ds = gdal.Open('utah_ecoIII60.tif') 182 | eco_band = eco_ds.GetRasterBand(1) 183 | eco_data = eco_band.ReadAsArray().flatten() 184 | eco_bins = get_bins(eco_data) 185 | 186 | lc_ds = gdal.Open('landcover60.tif') 187 | lc_band = lc_ds.GetRasterBand(1) 188 | lc_data = lc_band.ReadAsArray().flatten() 189 | lc_bins = get_bins(lc_data) 190 | 191 | # Function to calculate mode. 192 | def my_mode(data): 193 | return scipy.stats.mode(data)[0] 194 | 195 | # Get the histogram. 196 | modes, bins, bn = scipy.stats.binned_statistic( 197 | eco_data, lc_data, my_mode, eco_bins) 198 | print(modes) 199 | 200 | 201 | ########################### 11.3 Resampling Data ########################### 202 | 203 | # Make some test data. 204 | data = np.reshape(np.arange(24), (4, 6)) 205 | print(data) 206 | 207 | # Keep every other cell. 208 | print(data[::2, ::2]) 209 | 210 | # Keep every other cell, but start at the second row and column. 211 | print(data[1::2, 1::2]) 212 | 213 | # Repeat each column. 214 | print(np.repeat(data, 2, 1)) 215 | 216 | # Repeat columns and rows. 217 | print(np.repeat(np.repeat(data, 2, 0), 2, 1)) 218 | 219 | # Import the listing so we can use get_indices. 220 | import listing11_12 221 | 222 | # Here's a small sample grid so you can see what's happening. 223 | fn = os.path.join(data_dir, 'misc', 'smallgrid.tif') 224 | 225 | ds = gdal.Open(fn) 226 | data = ds.ReadAsArray() 227 | x, y = listing11_12.get_indices(ds, 25, -25) 228 | new_data = data[y.astype(int), x.astype(int)] 229 | 230 | print(data) 231 | print(new_data) 232 | 233 | 234 | #################### Resampling with GDAL command line utilities ########################### 235 | 236 | import subprocess 237 | 238 | args = [ 239 | 'gdalwarp', 240 | '-tr', '0.02', '0.02', 241 | '-r', 'bilinear', 242 | 'everest.tif', 'everest_resample.tif'] 243 | result = subprocess.call(args) 244 | -------------------------------------------------------------------------------- /Chapter9/chapter9.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from osgeo import gdal 4 | 5 | 6 | # Set this variable to your osgeopy-data directory so that the following 7 | # examples will work without editing. We'll use the os.path.join() function 8 | # to combine this directory and the filenames to make a complete path. Of 9 | # course, you can type the full path to the file for each example if you'd 10 | # prefer. 11 | data_dir = r'D:\osgeopy-data' 12 | # data_dir = 13 | 14 | 15 | 16 | ######################## 9.3 Reading partial datasets ####################### 17 | 18 | # Open a Landsat band. 19 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 20 | ds = gdal.Open('p047r027_7t20000730_z10_nn10.tif') 21 | band = ds.GetRasterBand(1) 22 | 23 | # Read in 3 rows and 6 columns starting at row 6000 and column 1400. 24 | data = band.ReadAsArray(1400, 6000, 6, 3) 25 | print(data) 26 | 27 | # Convert the data to floating point using numpy. 28 | data = band.ReadAsArray(1400, 6000, 6, 3).astype(float) 29 | print(data) 30 | 31 | # Or convert them to float by reading them into a floating point array. 32 | data = np.empty((3, 6), dtype=float) 33 | band.ReadAsArray(1400, 6000, 6, 3, buf_obj=data) 34 | print(data) 35 | 36 | # Write these few pixels to the middle of a tiny dummy raster (this 37 | # isn't exactly like the text example because that would be hard to see 38 | # what actually happened). You'll be able to see it best if you open the 39 | # output in GIS software. 40 | test_ds = gdal.GetDriverByName('GTiff').Create('test.tif', 10, 10) 41 | band2 = test_ds.GetRasterBand(1) 42 | band2.WriteArray(data, 4, 6) 43 | del test_ds 44 | 45 | 46 | ######################## Access window out of range ######################### 47 | 48 | # Try reading 5 rows and columns from the test image you just made, but 49 | # start at row 8 and column 2. This will fail because it's trying to read 50 | # rows 8 through 13, but there are only 10 rows. 51 | ds = gdal.Open('test.tif') 52 | band = ds.GetRasterBand(1) 53 | data = band.ReadAsArray(8, 2, 5, 5) 54 | 55 | # What happens if you try to write more data than there is room for? First 56 | # create an array of fake data. 57 | data = np.reshape(np.arange(25), (5,5)) 58 | print(data) 59 | 60 | # Now try to write it into the same area we just failed to read data from. 61 | # That fails, too. 62 | band.WriteArray(data, 8, 2) 63 | 64 | 65 | ##################### 9.3.1 Using real-world coordinates #################### 66 | 67 | # Get the geotransform from one of the Landsat bands. 68 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 69 | ds = gdal.Open('p047r027_7t20000730_z10_nn10.tif') 70 | band = ds.GetRasterBand(1) 71 | gt = ds.GetGeoTransform() 72 | print(gt) 73 | 74 | # Now get the inverse geotransform. The original can be used to convert 75 | # offsets to real-world coordinates, and the inverse can be used to convert 76 | # real-world coordinates to offsets. 77 | 78 | # GDAL 1.x: You get a success flag and the geotransform. 79 | success, inv_gt = gdal.InvGeoTransform(gt) 80 | print(success, inv_gt) 81 | 82 | # GDAL 2.x: You get the geotransform or None 83 | inv_gt = gdal.InvGeoTransform(gt) 84 | print(inv_gt) 85 | 86 | # Use the inverset geotransform to get some pixel offsets from real-world 87 | # UTM coordinates (since that's what the Landsat image uses). The offsets 88 | # are returned as floating point. 89 | offsets = gdal.ApplyGeoTransform(inv_gt, 465200, 5296000) 90 | print(offsets) 91 | 92 | # Convert the offsets to integers. 93 | xoff, yoff = map(int, offsets) 94 | print(xoff, yoff) 95 | 96 | # And use them to read a pixel value. 97 | value = band.ReadAsArray(xoff, yoff, 1, 1)[0,0] 98 | print(value) 99 | 100 | # Reading in one pixel at a time is really inefficient if you need to read 101 | # a lot of pixels, though, so here's how you could do it by reading in all 102 | # of the pixel values first and then pulling out the one you need. 103 | data = band.ReadAsArray() 104 | x, y = map(int, gdal.ApplyGeoTransform(inv_gt, 465200, 5296000)) 105 | value = data[yoff, xoff] 106 | print(value) 107 | 108 | 109 | ############################ 9.3.2 Resampling data ########################## 110 | 111 | # Get the first band from the raster created with listing 8.1. 112 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 113 | ds = gdal.Open('nat_color.tif') 114 | band = ds.GetRasterBand(1) 115 | 116 | # Read in 2 rows and 3 columns. 117 | original_data = band.ReadAsArray(1400, 6000, 3, 2) 118 | print(original_data) 119 | 120 | # Now resample those same 2 rows and 3 columns to a smaller pixel size by 121 | # doubling the number of rows and columns to read (now 4 rows and 6 columns). 122 | resampled_data = band.ReadAsArray(1400, 6000, 3, 2, 6, 4) 123 | print(resampled_data) 124 | 125 | # Read in 4 rows and 6 columns. 126 | original_data2 = band.ReadAsArray(1400, 6000, 6, 4) 127 | print(original_data2) 128 | 129 | # Now resample those same 4 rows and 6 columns to a larger pixel size by 130 | # halving the number of rows and columns to read (now 2 rows and 3 columns). 131 | resampled_data2 = np.empty((2, 3), np.int) 132 | band.ReadAsArray(1400, 6000, 6, 4, buf_obj=resampled_data2) 133 | print(resampled_data2) 134 | 135 | 136 | 137 | ############################# 9.4 Byte sequences ############################ 138 | 139 | # Read a few pixels as a byte string from the raster created with listing 8.1. 140 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 141 | ds = gdal.Open('nat_color.tif') 142 | data = ds.ReadRaster(1400, 6000, 2, 2, band_list=[1]) 143 | print(data) 144 | 145 | # Pull the first value out. It will be converted from a byte string to a 146 | # number. 147 | print(data[0]) 148 | 149 | # Try to change the value of that first pixel. This will fail because you 150 | # can't change byte strings. 151 | data[0] = 50 152 | 153 | # Convert the byte string to a byte array and then change the first value. 154 | bytearray_data = bytearray(data) 155 | bytearray_data[0] = 50 156 | print(bytearray_data[0]) 157 | 158 | # Convert the byte string to tuple of pixel values. 159 | import struct 160 | tuple_data = struct.unpack('B' * 4, data) 161 | print(tuple_data) 162 | 163 | # Convert the tuple to a numpy array. 164 | numpy_data1 = np.array(tuple_data) 165 | print(numpy_data1) 166 | 167 | # Conver the byte string to a numpy array. 168 | numpy_data2 = np.fromstring(data, np.int8) 169 | print(numpy_data2) 170 | 171 | # Reshape one of the numpy arrays so it has 2 rows and 2 columns, just like 172 | # the original data we read in. 173 | reshaped_data = np.reshape(numpy_data2, (2,2)) 174 | print(reshaped_data) 175 | 176 | # Write our little byte string to the middle of a tiny dummy raster (this 177 | # isn't exactly like the text example because that would be hard to see 178 | # what actually happened). You'll be able to see it best if you open the 179 | # output in GIS software. 180 | test_ds = gdal.GetDriverByName('GTiff').Create('test2.tif', 10, 10) 181 | test_ds.WriteRaster(4, 6, 2, 2, data, band_list=[1]) 182 | del test_ds 183 | 184 | 185 | 186 | ############################### 9.5 Subdatasets ############################# 187 | 188 | # Get the subdatasets from a MODIS file. 189 | os.chdir(os.path.join(data_dir, 'Modis')) 190 | ds = gdal.Open('MYD13Q1.A2014313.h20v11.005.2014330092746.hdf') 191 | subdatasets = ds.GetSubDatasets() 192 | print('Number of subdatasets: {}'.format(len(subdatasets))) 193 | for sd in subdatasets: 194 | print('Name: {0}\nDescription:{1}\n'.format(*sd)) 195 | 196 | # Open the the first subdataset in the Modis file. 197 | ndvi_ds = gdal.Open(subdatasets[0][0]) 198 | 199 | # Make sure that it worked by by printing out the dimensions. You can use 200 | # ndvi_ds just like any other dataset. 201 | print('Dataset dimensions: {} {}'.format(ndvi_ds.RasterXSize, ndvi_ds.RasterYSize)) 202 | 203 | # For example, you still need to get the band before you can read data. 204 | ndvi_band = ndvi_ds.GetRasterBand(1) 205 | print('Band dimensions: {} {}'.format(ndvi_band.XSize, ndvi_band.YSize)) 206 | 207 | 208 | 209 | ################################### 9.6 WMS ################################# 210 | 211 | ds = gdal.Open('listing9_6.xml') 212 | gdal.GetDriverByName('PNG').CreateCopy(r'D:\Temp\liberty.png', ds) 213 | -------------------------------------------------------------------------------- /Chapter13/chapter13.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from osgeo import gdal, ogr 4 | import matplotlib.pyplot as plt 5 | import mapnik 6 | 7 | 8 | # Set this variable to your osgeopy-data directory so that the following 9 | # examples will work without editing. We'll use the os.path.join() function 10 | # to combine this directory and the filenames to make a complete path. Of 11 | # course, you can type the full path to the file for each example if you'd 12 | # prefer. 13 | data_dir = r'D:\osgeopy-data' 14 | # data_dir = 15 | 16 | 17 | ############################## 13.1 Matplotlib ########################### 18 | 19 | import matplotlib.pyplot as plt 20 | 21 | # Turn interactive mode on if you want. 22 | #plt.ion() 23 | 24 | 25 | ######################### 13.1.1 Plotting vector data #################### 26 | 27 | # Plot a line. 28 | x = range(10) 29 | y = [i * i for i in x] 30 | plt.plot(x, y) 31 | plt.show() 32 | 33 | # Plot dots. 34 | plt.plot(x, y, 'ro', markersize=10) 35 | plt.show() 36 | 37 | # Make a polygon. 38 | x = list(range(10)) 39 | y = [i * i for i in x] 40 | x.append(0) 41 | y.append(0) 42 | plt.plot(x, y, lw=5) 43 | plt.show() 44 | 45 | # Draw polygons as patches instead. 46 | from matplotlib.path import Path 47 | import matplotlib.patches as patches 48 | 49 | coords = [(0, 0), (0.5, 1), (1, 0), (0, 0)] 50 | codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO] 51 | path = Path(coords, codes) 52 | patch = patches.PathPatch(path, facecolor='red') 53 | plt.axes().add_patch(patch) 54 | plt.show() 55 | 56 | # This one has a hole in it. The inner ring must go in the 57 | # opposite direction of the outer ring. In this example, 58 | # outer_coords are clockwise and inner_coords are 59 | # counter-clockwise. 60 | outer_coords = [(0, 0), (0.5, 1), (1, 0), (0, 0)] 61 | outer_codes = [Path.MOVETO, Path.LINETO, 62 | Path.LINETO, Path.LINETO] 63 | inner_coords = [(0.4, 0.4), (0.5, 0.2), 64 | (0.6, 0.4), (0.4, 0.4)] 65 | inner_codes = [Path.MOVETO, Path.LINETO, 66 | Path.LINETO, Path.LINETO] 67 | coords = np.concatenate((outer_coords, inner_coords)) 68 | codes = np.concatenate((outer_codes, inner_codes)) 69 | path = Path(coords, codes) 70 | patch = patches.PathPatch(path, facecolor='red') 71 | plt.axes().add_patch(patch) 72 | plt.show() 73 | 74 | 75 | 76 | ################################## Animation ############################ 77 | # Animate the albatross GPS locations from chapter 7. 78 | 79 | # First set things up. 80 | ds = ogr.Open(os.path.join(data_dir, 'Galapagos')) 81 | gps_lyr = ds.GetLayerByName('albatross_lambert') 82 | extent = gps_lyr.GetExtent() 83 | fig = plt.figure() 84 | plt.axis('equal') 85 | plt.xlim(extent[0] - 1000, extent[1] + 1000) 86 | plt.ylim(extent[2] - 1000, extent[3] + 1000) 87 | plt.gca().get_xaxis().set_ticks([]) 88 | plt.gca().get_yaxis().set_ticks([]) 89 | 90 | # Plot the background continents. 91 | import ch13funcs 92 | land_lyr = ds.GetLayerByName('land_lambert') 93 | row = next(land_lyr) 94 | geom = row.geometry() 95 | for i in range(geom.GetGeometryCount()): 96 | ch13funcs.plot_polygon(geom.GetGeometryRef(i)) 97 | 98 | # Get the timestamps for one of the birds. 99 | timestamps, coordinates = [], [] 100 | gps_lyr.SetAttributeFilter("tag_id = '2131-2131'") 101 | for row in gps_lyr: 102 | timestamps.append(row.GetField('timestamp')) 103 | coordinates.append((row.geometry().GetX(), row.geometry().GetY())) 104 | 105 | # Initialize the points and annotation. 106 | point = plt.plot(None, None, 'o')[0] 107 | label = plt.gca().annotate('', (0.25, 0.95), xycoords='axes fraction') 108 | label.set_animated(True) 109 | 110 | # Write a function that tells matplotlib which items will change. 111 | def init(): 112 | point.set_data(None, None) 113 | return point, label 114 | 115 | # Write a function to update the point location and annotation. 116 | def update(i, point, label, timestamps, coordinates): 117 | label.set_text(timestamps[i]) 118 | point.set_data(coordinates[i][0], coordinates[i][1]) 119 | return point, label 120 | 121 | # Finally run the animation. 122 | import matplotlib.animation as animation 123 | a = animation.FuncAnimation( 124 | fig, update, frames=len(timestamps), init_func=init, 125 | fargs=(point, label, timestamps, coordinates), 126 | interval=25, blit=True, repeat=False) 127 | plt.show() 128 | 129 | # Write a function that rounds timestamps. 130 | from datetime import datetime, timedelta 131 | def round_timestamp(ts, minutes=60): 132 | ts += timedelta(minutes=minutes/2.0) 133 | ts -= timedelta( 134 | minutes=ts.minute % minutes, seconds=ts.second, 135 | microseconds=ts.microsecond) 136 | return ts 137 | 138 | # Initialize the timestamp and coordinates lists with the first set of values. 139 | gps_lyr.SetAttributeFilter("tag_id = '2131-2131'") 140 | time_format = '%Y-%m-%d %H:%M:%S.%f' 141 | row = next(gps_lyr) 142 | timestamp = datetime.strptime(row.GetField('timestamp'), time_format) 143 | timestamp = round_timestamp(timestamp) 144 | timestamps = [timestamp] 145 | coordinates = [(row.geometry().GetX(), row.geometry().GetY())] 146 | 147 | # Now get timestamps and coordinates, but fill in empty time slots with 148 | # filler data. 149 | hour = timedelta(hours=1) 150 | for row in gps_lyr: 151 | timestamp = datetime.strptime(row.GetField('timestamp'), time_format) 152 | timestamp = round_timestamp(timestamp) 153 | while timestamps[-1] < timestamp: 154 | timestamps.append(timestamps[-1] + hour) 155 | coordinates.append((None, None)) 156 | coordinates[-1] = (row.geometry().GetX(), row.geometry().GetY()) 157 | 158 | # Change the update function so it only updates coordinates if there are 159 | # some for the current timestamp. 160 | def update(i, point, label, timestamps, coordinates): 161 | label.set_text(timestamps[i]) 162 | if coordinates[i][0] is not None: 163 | point.set_data(coordinates[i][0], coordinates[i][1]) 164 | return point, label 165 | 166 | # Run the animation again, but now it has constant time intervals. 167 | a = animation.FuncAnimation( 168 | fig, update, frames=len(timestamps), init_func=init, 169 | fargs=(point, label, timestamps, coordinates), 170 | interval=25, blit=True, repeat=False) 171 | plt.show() 172 | 173 | ######################### 13.1.2 Plotting raster data #################### 174 | 175 | ds = gdal.Open(r'D:\osgeopy-data\Washington\dem\sthelens_utm.tif') 176 | data = ds.GetRasterBand(1).ReadAsArray() 177 | 178 | # Default color ramp 179 | plt.imshow(data) 180 | plt.show() 181 | 182 | # Grayscale 183 | plt.imshow(data, cmap='gray') 184 | plt.show() 185 | 186 | # Use the function from listing 13.5 to get overview data and plot it. 187 | from listing13_5 import get_overview_data 188 | fn = r'D:\osgeopy-data\Landsat\Washington\p047r027_7t20000730_z10_nn10.tif' 189 | data = get_overview_data(fn) 190 | data = np.ma.masked_equal(data, 0) 191 | plt.imshow(data, cmap='gray') 192 | plt.show() 193 | 194 | # Plot it using stretched data. 195 | mean = np.mean(data) 196 | std_range = np.std(data) * 2 197 | plt.imshow(data, cmap='gray', vmin=mean-std_range, vmax=mean+std_range) 198 | plt.show() 199 | 200 | # Try plotting 3 bands. 201 | os.chdir(r'D:\osgeopy-data\Landsat\Washington') 202 | red_fn = 'p047r027_7t20000730_z10_nn30.tif' 203 | green_fn = 'p047r027_7t20000730_z10_nn20.tif' 204 | blue_fn = 'p047r027_7t20000730_z10_nn10.tif' 205 | 206 | red_data = get_overview_data(red_fn) 207 | green_data = get_overview_data(green_fn) 208 | blue_data = get_overview_data(blue_fn) 209 | data = np.dstack((red_data, green_data, blue_data)) 210 | plt.imshow(data) 211 | plt.show() 212 | 213 | 214 | # Plot 3 stretched bands. 215 | from listing13_6 import stretch_data 216 | red_data = stretch_data(get_overview_data(red_fn), 2) 217 | green_data = stretch_data(get_overview_data(green_fn), 2) 218 | blue_data = stretch_data(get_overview_data(blue_fn), 2) 219 | alpha = np.where(red_data + green_data + blue_data > 0, 1, 0) 220 | data = np.dstack((red_data, green_data, blue_data, alpha)) 221 | plt.imshow(data) 222 | plt.show() 223 | 224 | 225 | ######################### 13.1.3 Plotting 3D data #################### 226 | 227 | # See listing 13.7. 228 | 229 | 230 | ##################### 13.2.2 Storing mapnik data as xml ############### 231 | 232 | # Run listing 13.10 in order to save the xml file you need with the 233 | # correct paths for your machine. 234 | 235 | # Load the xml file and create an image from it. 236 | m = mapnik.Map(400, 300) 237 | m.zoom_to_box(mapnik.Box2d(-90.3, 29.7, -89.5, 30.3)) 238 | mapnik.load_map(m, r'd:\temp\nola_map.xml') 239 | mapnik.render_to_file(m, r'd:\temp\nola4.png') 240 | 241 | 242 | # See listing 13.9-edited to see the hydrography xml in action. 243 | -------------------------------------------------------------------------------- /ospybook/ospybook/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | try: 4 | import numpy as np 5 | except: 6 | pass 7 | 8 | from osgeo import ogr, gdal, gdalconst #, osr 9 | 10 | def compute_overview_levels(band): 11 | """Return an appropriate list of overview levels.""" 12 | max_dim = max(band.XSize, band.YSize) 13 | overviews = [] 14 | level = 1 15 | while max_dim > 256: 16 | level *= 2 17 | overviews.append(level) 18 | max_dim /= 2 19 | return overviews 20 | 21 | def copy_datasource(source_fn, target_fn): 22 | """Copy an ogr data source.""" 23 | ds = ogr.Open(source_fn, 0) 24 | if ds is None: 25 | raise OSError('Could not open {0} for copying.'.format(source_fn)) 26 | if os.path.exists(target_fn): 27 | ds.GetDriver().DeleteDataSource(target_fn) 28 | ds.GetDriver().CopyDataSource(ds, target_fn) 29 | 30 | def get_shp_geom(fn): 31 | """Convenience function to get the first geometry from a shapefile.""" 32 | lyr, ds = _get_layer(fn) 33 | feat = lyr.GetNextFeature() 34 | return feat.geometry().Clone() 35 | 36 | def has_spatialite(): 37 | """Determine if the current GDAL is built with SpatiaLite support.""" 38 | use_exceptions = ogr.GetUseExceptions() 39 | ogr.UseExceptions() 40 | try: 41 | ds = ogr.GetDriverByName('Memory').CreateDataSource('memory') 42 | sql = '''SELECT sqlite_version(), spatialite_version()''' 43 | lyr = ds.ExecuteSQL(sql, dialect='SQLite') 44 | return True 45 | except Exception as e: 46 | return False 47 | finally: 48 | if not use_exceptions: 49 | ogr.DontUseExceptions() 50 | 51 | def make_raster(in_ds, fn, data, data_type, nodata=None): 52 | """Create a one-band GeoTIFF. 53 | 54 | in_ds - datasource to copy projection and geotransform from 55 | fn - path to the file to create 56 | data - NumPy array containing data to write 57 | data_type - output data type 58 | nodata - optional NoData value 59 | """ 60 | driver = gdal.GetDriverByName('GTiff') 61 | out_ds = driver.Create( 62 | fn, in_ds.RasterXSize, in_ds.RasterYSize, 1, data_type) 63 | out_ds.SetProjection(in_ds.GetProjection()) 64 | out_ds.SetGeoTransform(in_ds.GetGeoTransform()) 65 | out_band = out_ds.GetRasterBand(1) 66 | if nodata is not None: 67 | out_band.SetNoDataValue(nodata) 68 | out_band.WriteArray(data) 69 | out_band.FlushCache() 70 | out_band.ComputeStatistics(False) 71 | return out_ds 72 | 73 | def make_slices(data, win_size): 74 | """Return a list of slices given a window size. 75 | 76 | data - two-dimensional array to get slices from 77 | win_size - tuple of (rows, columns) for the moving window 78 | """ 79 | rows = data.shape[0] - win_size[0] + 1 80 | cols = data.shape[1] - win_size[1] + 1 81 | slices = [] 82 | for i in range(win_size[0]): 83 | for j in range(win_size[1]): 84 | slices.append(data[i:rows+i, j:cols+j]) 85 | return slices 86 | 87 | def make_masked_slices(band, win_size): 88 | """Return a list of slices given a window size. 89 | 90 | band - band to get slices from 91 | win_size - tuple of (rows, columns) for the moving window 92 | """ 93 | rows = band.YSize + win_size[0] - 1 94 | cols = band.XSize + win_size[1] - 1 95 | data = np.ma.masked_all((rows, cols), np.float) 96 | 97 | edge_rows, edge_cols = [int(n / 2) for n in win_size] 98 | data[edge_rows:-edge_rows, edge_cols:-edge_cols] = band.ReadAsArray() 99 | return data 100 | 101 | 102 | def print_attributes(lyr_or_fn, n=None, fields=None, geom=True, reset=True): 103 | """Print attribute values in a layer. 104 | 105 | lyr_or_fn - OGR layer object or filename to datasource (will use 1st layer) 106 | n - optional number of features to print; default is all 107 | fields - optional list of case-sensitive field names to print; default 108 | is all 109 | geom - optional boolean flag denoting whether geometry type is printed; 110 | default is True 111 | reset - optional boolean flag denoting whether the layer should be reset 112 | - to the first record before printing; default is True 113 | """ 114 | lyr, ds = _get_layer(lyr_or_fn) 115 | if reset: 116 | lyr.ResetReading() 117 | 118 | n = n or lyr.GetFeatureCount() 119 | geom = geom and lyr.GetGeomType() != ogr.wkbNone 120 | fields = fields or [field.name for field in lyr.schema] 121 | data = [['FID'] + fields] 122 | if geom: 123 | data[0].insert(1, 'Geometry') 124 | feat = lyr.GetNextFeature() 125 | while feat and len(data) <= n: 126 | data.append(_get_atts(feat, fields, geom)) 127 | feat = lyr.GetNextFeature() 128 | lens = map(lambda i: max(map(lambda j: len(str(j)), i)), zip(*data)) 129 | format_str = ''.join(map(lambda x: '{{:<{}}}'.format(x + 4), lens)) 130 | for row in data: 131 | try: 132 | print(format_str.format(*row)) 133 | except UnicodeEncodeError: 134 | e = sys.stdout.encoding 135 | print(codecs.decode(format_str.format(*row).encode(e, 'replace'), e)) 136 | print('{0} of {1} features'.format(min(n, lyr.GetFeatureCount()), lyr.GetFeatureCount())) 137 | if reset: 138 | lyr.ResetReading() 139 | 140 | def print_capabilities(item): 141 | """Print capabilities for a driver, datasource, or layer.""" 142 | if isinstance(item, ogr.Driver): 143 | _print_capabilites(item, 'Driver', 'ODrC') 144 | elif isinstance(item, ogr.DataSource): 145 | _print_capabilites(item, 'DataSource', 'ODsC') 146 | elif isinstance(item, ogr.Layer): 147 | _print_capabilites(item, 'Layer', 'OLC') 148 | else: 149 | print('Unsupported item') 150 | 151 | def print_drivers(): 152 | """Print a list of available drivers.""" 153 | for i in range(ogr.GetDriverCount()): 154 | driver = ogr.GetDriver(i) 155 | writeable = driver.TestCapability(ogr.ODrCCreateDataSource) 156 | print('{0} ({1})'.format(driver.GetName(), 157 | 'read/write' if writeable else 'readonly')) 158 | 159 | def print_layers(fn): 160 | """Print a list of layers in a data source. 161 | 162 | fn - path to data source 163 | """ 164 | ds = ogr.Open(fn, 0) 165 | if ds is None: 166 | raise OSError('Could not open {}'.format(fn)) 167 | for i in range(ds.GetLayerCount()): 168 | lyr = ds.GetLayer(i) 169 | print('{0}: {1} ({2})'.format(i, lyr.GetName(), 170 | _geom_constants[lyr.GetGeomType()])) 171 | 172 | def stack_bands(filenames): 173 | """Returns a 3D array containing all band data from all files.""" 174 | bands = [] 175 | for fn in filenames: 176 | ds = gdal.Open(fn) 177 | for i in range(1, ds.RasterCount + 1): 178 | bands.append(ds.GetRasterBand(i).ReadAsArray()) 179 | return np.dstack(bands) 180 | 181 | def _geom_str(geom): 182 | """Get a geometry string for printing attributes.""" 183 | if geom.GetGeometryType() == ogr.wkbPoint: 184 | return 'POINT ({:.3f}, {:.3f})'.format(geom.GetX(), geom.GetY()) 185 | else: 186 | return geom.GetGeometryName() 187 | 188 | def _get_atts(feature, fields, geom): 189 | """Get attribute values from a feature.""" 190 | data = [feature.GetFID()] 191 | geometry = feature.geometry() 192 | if geom and geometry: 193 | data.append(_geom_str(geometry)) 194 | values = feature.items() 195 | data += [values[field] for field in fields] 196 | return data 197 | 198 | def _get_layer(lyr_or_fn): 199 | """Get the datasource and layer from a filename.""" 200 | if type(lyr_or_fn) is str: 201 | ds = ogr.Open(lyr_or_fn) 202 | if ds is None: 203 | raise OSError('Could not open {0}.'.format(lyr_or_fn)) 204 | return ds.GetLayer(), ds 205 | else: 206 | return lyr_or_fn, None 207 | 208 | def _print_capabilites(item, name, prefix): 209 | """Print capabilities for a driver, datasource, or layer. 210 | 211 | item - item to test 212 | name - name of the type of item 213 | prefix - prefix of the ogr constants to use for testing 214 | """ 215 | print('*** {0} Capabilities ***'.format(name)) 216 | for c in filter(lambda x: x.startswith(prefix), dir(ogr)): 217 | print('{0}: {1}'.format(c, item.TestCapability(ogr.__dict__[c]))) 218 | 219 | _geom_constants = {} 220 | _ignore = ['wkb25DBit', 'wkb25Bit', 'wkbXDR', 'wkbNDR'] 221 | for c in filter(lambda x: x.startswith('wkb'), dir(ogr)): 222 | if c not in _ignore: 223 | _geom_constants[ogr.__dict__[c]] = c[3:] 224 | 225 | 226 | def get_constant_name(lib, prefix, value): 227 | for c in filter(lambda x: x.startswith(prefix), dir(lib)): 228 | if lib.__dict__[c] == value: 229 | return c 230 | return None 231 | 232 | def get_constant_value(lib, name): 233 | try: 234 | return lib.__dict__[name] 235 | except KeyError: 236 | return None 237 | 238 | 239 | def get_gdal_constant_name(prefix, value): 240 | return get_constant_name(gdal, prefix + '_', value) 241 | 242 | def get_gdal_constant_value(name): 243 | return get_constant_value(gdal, name) 244 | 245 | def get_ogr_constant_name(prefix, value): 246 | return get_constant_name(ogr, prefix, value) 247 | 248 | def get_ogr_constant_value(name): 249 | return get_constant_value(ogr, name) 250 | 251 | # def get_osr_constant_name(prefix, value): 252 | # if not prefix.startswith('SRS'): 253 | # prefix = 'SRS_' + prefix 254 | # return get_constant_name(osr, prefix + '_', value) 255 | 256 | # def get_osr_constant_value(name): 257 | # return get_constant_value(osr, name) 258 | -------------------------------------------------------------------------------- /Chapter2/chapter2.py: -------------------------------------------------------------------------------- 1 | # These lines that begin with # are comments and will be ignored by Python. 2 | 3 | # I use the print function in this code, even though I don't in the book text, 4 | # so that you can run it as a regular script and still get the output. You only 5 | # get output without using print if you're using the interactive window. 6 | 7 | 8 | ###################### 2.2 Basic structure of a script ###################### 9 | 10 | # Import the random module and use it to get a random number 11 | import random 12 | print(random.gauss(0, 1)) 13 | 14 | 15 | ############################### 2.3 Variables ############################### 16 | 17 | # Creating a variable 18 | n = 10 19 | print(n) 20 | 21 | # Changing n from integer to string 22 | n = 'Hello world' 23 | print(n) 24 | 25 | # Attempt to add a string (n) and integer together 26 | msg = n + 1 27 | 28 | # Test for equality 29 | n = 10 30 | print(n == 10) 31 | print(n == 15) 32 | 33 | 34 | ############################### 2.4 Data types ############################## 35 | 36 | #################### 2.4.1 Booleans 37 | 38 | print(True or False) 39 | print(not False) 40 | print(True and False) 41 | print(True and not False) 42 | 43 | #################### 2.4.2 Numeric types 44 | 45 | print(27 / 7) # Integer (Python 2.7) or floating point (Python 3) math 46 | print(27.0 / 7.0) # Floating point math in both 47 | print(27 / 7.0) # Floating point math in both 48 | 49 | print(27 / 7) # Integer (Python 2.7) or floating point (Python 3) math 50 | print(27 // 7) # Force integer point math in Python 3 51 | 52 | # Convert between integer and float 53 | print(float(27)) 54 | print(int(27.9)) 55 | 56 | print(round(27.9)) 57 | 58 | #################### 2.4.3 Strings 59 | 60 | s = 'Hello world' 61 | print(s) 62 | 63 | # String that contains quotes 64 | sql = "SELECT * FROM cities WHERE country = 'Canada'" 65 | print(sql) 66 | 67 | # Does not work because the first single quote ends the string 68 | print('Don't panic!') 69 | 70 | # This one works because the backslash tells Python to include it in the string 71 | print('Don\'t panic!') 72 | 73 | # Join two strings 74 | s = 'Beam me up ' + 'Scotty' 75 | print(s) 76 | 77 | # Formatting strings using indexes in the placeholders 78 | print('I wish I were as smart as {0} {1}'.format('Albert', 'Einstein')) 79 | print('I wish I were as smart as {1}, {0}'.format('Albert', 'Einstein')) 80 | 81 | # Escape characters (\t means tab, \n means newline) 82 | print('Title:\tMoby Dick\nAuthor:\tHerman Melville') 83 | 84 | #################### Windows filenames 85 | 86 | # Import the os module for the next couple of Windows examples 87 | import os 88 | 89 | # Backslashes in Windows filenames 90 | # Change the path to a file that exists on your Windows machine so that there 91 | # is a \t in the path (so a temp folder is good) 92 | print(os.path.exists('d:\temp\cities.csv')) 93 | 94 | # To see what Python thinks you're saying 95 | print('d:\temp\cities.csv') 96 | 97 | # Three ways to fix the problem 98 | print(os.path.exists('d:/temp/cities.csv')) # Use forward slashes instead 99 | print(os.path.exists('d:\\temp\\cities.csv')) # Use double-backslashes 100 | print(os.path.exists(r'd:\temp\cities.csv')) # Prefix the string with r 101 | 102 | #################### 2.4.4 Lists and tuples 103 | 104 | # Create a list 105 | data = [5, 'Bob', 'yellow', -43, 'cat'] 106 | print(data) 107 | 108 | # Get stuff out of it by index 109 | print(data[0]) 110 | print(data[2]) 111 | print(data[-1]) 112 | print(data[-3]) 113 | 114 | # Get sublists 115 | print(data[1:3]) 116 | print(data[-4:-1]) 117 | 118 | # Change list values 119 | data[2] = 'red' 120 | print(data) 121 | data[0:2] = [2, 'Mary'] 122 | print(data) 123 | 124 | # Append and delete values 125 | data.append('dog') 126 | print(data) 127 | del data[1] 128 | print(data) 129 | 130 | # Get the length of a list 131 | print(len(data)) 132 | 133 | # Find out if values are in a list 134 | print(2 in data) 135 | print('Mary' in data) 136 | 137 | # Create a tuple and get values out of ities 138 | data = (5, 'Bob', 'yellow', -43, 'cat') 139 | print(data) 140 | print(data[2]) 141 | print(data[-3]) 142 | print(data[1:3]) 143 | 144 | # Get the length of a tuple and check if it contains a value 145 | print(len(data)) 146 | print('Bob' in data) 147 | 148 | # Try, and fail, to change a value of a tuple 149 | data[0] = 10 150 | 151 | ####################### Error messages are your friend ####################### 152 | 153 | # This will cause an error because you cannot change the value of a tuple 154 | data = (5, 'Bob', 'yellow', -43, 'cat') 155 | data[0] = 10 156 | 157 | # Function that the next two examples use. It takes two values as parameters 158 | # and returns their sum. 159 | def add(n1, n2): 160 | return n1 + n2 161 | 162 | # This one will work 163 | x = add(3, 5) 164 | print(x) 165 | 166 | # This one fails because '1' is a string, which cannot be added to a number (x) 167 | y = add(x, '1') 168 | print(y) 169 | 170 | #################### 2.4.5 Sets 171 | 172 | # Create a set 173 | data = set(['book', 6, 13, 13, 'movie']) 174 | print(data) 175 | 176 | # Add stuff to the set; 'movie' will not be added again 177 | data.add('movie') 178 | data.add('game') 179 | print(data) 180 | 181 | # Check if a value is in the set 182 | print(13 in data) 183 | 184 | # Create another set and union and intersect it with the first set 185 | info = set(['6', 6, 'game', 42]) 186 | print(data.union(info)) # All values from both sets 187 | print(data.intersection(info)) # Only values contained in both 188 | 189 | #################### 2.4.6 Dictionaries 190 | 191 | # Create a dictionary and get values out of it 192 | data = {'color': 'red', 'lucky number': 42, 1: 'one'} 193 | print(data) 194 | print(data[1]) 195 | print(data['lucky number']) 196 | 197 | # Add a value 198 | data[5] = 'candy' 199 | print(data) 200 | 201 | # Change a value 202 | data['color'] = 'green' 203 | print(data) 204 | 205 | # Delete a value 206 | del data[1] 207 | print(data) 208 | 209 | # Check if a key is in the dictionary 210 | print('color' in data) 211 | 212 | 213 | ############################# 2.5 Flow of control ############################ 214 | 215 | #################### 2.5.1 If statements 216 | 217 | # Print a different message depending on the value of n. Try changing the value 218 | # of n. 219 | n = 1 220 | if n == 1: 221 | print('n equals 1') 222 | else: 223 | print('n does not equal 1') 224 | print('This is not part of the condition') 225 | 226 | # Test multiple conditions. Try changing the value of n. 227 | n = 0 228 | if n == 1: 229 | print('n equals 1') 230 | elif n == 3: 231 | print('n does not equal 3') 232 | elif n > 5: 233 | print('n is greater than 5') 234 | else: 235 | print('what is n?') 236 | 237 | # Blank strings resolve to False 238 | if '': 239 | print('a blank string acts like True') 240 | else: 241 | print('a blank string acts like false') 242 | 243 | # Empty lists also resolve to false, while lists with stuff in them do not 244 | if [1]: 245 | print('a non-empty list acts like True') 246 | else: 247 | print('a non-empty list acts like False') 248 | 249 | #################### 2.5.2 While statements 250 | 251 | # Print the numbers 0 through 4 252 | n = 0 253 | while n < 5: 254 | print(n) 255 | n += 1 256 | 257 | #################### 2.5.3 For statements 258 | 259 | # Print a message for each name in the list 260 | names = ['Chris', 'Janet', 'Tami'] 261 | for name in names: 262 | print('Hello {}!'.format(name)) 263 | 264 | # Use range() to increment n 20 times 265 | n = 0 266 | for i in range(20): 267 | n += 1 268 | print(n) 269 | 270 | # Use range() to compute the factorial of 20 271 | n = 1 272 | for i in range(1, 21): 273 | n = n * i 274 | print(n) 275 | 276 | #################### 2.5.4 Break, continue, and else 277 | 278 | # Break out of a loop if i == 3 279 | for i in range(5): 280 | if i == 3: 281 | break 282 | print(i) 283 | 284 | # Go back to the beginning of the loop and skip the print function if i == 3 285 | for i in range(5): 286 | if i == 3: 287 | continue 288 | print(i) 289 | 290 | # Use a break statement to get out of the loop if we find the number 2... 291 | for i in [0, 5, 7, 2, 3]: 292 | if i == 2: 293 | print('Found it!') 294 | break 295 | else: 296 | print('Could not find 2') 297 | 298 | # ...but use an else clause to say we couldn't find it if it's not there 299 | for i in [0, 5, 7, 3]: 300 | if i == 2: 301 | print('Found it!') 302 | break 303 | else: 304 | print('Could not find 2') 305 | 306 | 307 | ################################ 2.6 Functions ############################### 308 | 309 | # Write a function to calculate factorials 310 | def factorial(n): 311 | answer = 1 312 | for i in range(1, n + 1): 313 | answer = answer * i 314 | return answer 315 | 316 | # Use the function like this: 317 | fact5 = factorial(5) 318 | print(fact5) 319 | 320 | 321 | # Change the function so it will optionally print the answer 322 | def factorial(n, print_it=False): 323 | answer = 1 324 | for i in range(1, n + 1): 325 | answer = answer * i 326 | if print_it: 327 | print('{0}! = {1}'.format(n, answer)) 328 | return answer 329 | 330 | # Use the function without printing the answer 331 | fact5 = factorial(5) 332 | 333 | # Use the function and have it print the answer 334 | fact5 = factorial(5, True) 335 | 336 | # Use the function from a module 337 | import myfuncs 338 | fact5 = myfuncs.factorial(5) 339 | print(fact5) 340 | 341 | 342 | ################################# 2.7 Classes ################################ 343 | 344 | # Import the datetime module and create a new object of the datetime class 345 | import datetime 346 | datetype = datetime.date 347 | mydate = datetype.today() 348 | print(mydate) 349 | 350 | # Use a function that belongs to the class to ask the datetime object what 351 | # day of the week it is 352 | print(mydate.weekday()) 353 | 354 | # Use another function to return a new date similar to the first one but with 355 | # a different year 356 | newdate = mydate.replace(year=2010) 357 | print(newdate) 358 | print(newdate.weekday()) 359 | -------------------------------------------------------------------------------- /Chapter5/chapter5.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from osgeo import ogr 4 | import ospybook as pb 5 | from ospybook.vectorplotter import VectorPlotter 6 | 7 | 8 | # Set this variable to your osgeopy-data directory so that the following 9 | # examples will work without editing. We'll use the os.path.join() function 10 | # to combine this directory and the filenames to make a complete path. Of 11 | # course, you can type the full path to the file for each example if you'd 12 | # prefer. 13 | # data_dir = r'D:\osgeopy-data' 14 | data_dir = 15 | 16 | 17 | 18 | ######################## 5.1 Attribute filters ############################# 19 | 20 | # Set up an interactive plotter. Because this is the most fun if you do it 21 | # interactively, you'll probably want to do it from the Python interactive 22 | # prompt. If you're going to run it as a script instead, you might want to use 23 | # a non-interactive plotter instead. Just remember to call draw() when you're 24 | # done. 25 | vp = VectorPlotter(True) 26 | 27 | # Get the countries shapefile layer 28 | ds = ogr.Open(os.path.join(data_dir, 'global')) 29 | lyr = ds.GetLayer('ne_50m_admin_0_countries') 30 | 31 | # Plot the countries with no fill and also print out the first 4 attribute 32 | # records. 33 | vp.plot(lyr, fill=False) 34 | pb.print_attributes(lyr, 4, ['name'], geom=False) 35 | 36 | # Apply a filter that finds countries in Asia and see how many records there 37 | # are now. 38 | lyr.SetAttributeFilter('continent = "Asia"') 39 | lyr.GetFeatureCount() 40 | 41 | # Draw the Asian countries in yellow and print out a few features. 42 | vp.plot(lyr, 'y') 43 | pb.print_attributes(lyr, 4, ['name'], geom=False) 44 | 45 | # You can still get a feature that is not in Asia by using its FID. 46 | lyr.GetFeature(2).GetField('name') 47 | 48 | # Set a new filter that selects South American countries and show the results 49 | # in blue. The old filter is no longer in effect. 50 | lyr.SetAttributeFilter('continent = "South America"') 51 | vp.plot(lyr, 'b') 52 | 53 | # Clear all attribute filters. 54 | lyr.SetAttributeFilter(None) 55 | lyr.GetFeatureCount() 56 | 57 | 58 | 59 | ########################## 5.2 Spatial filters ############################# 60 | 61 | # Set up an interactive plotter. 62 | vp = VectorPlotter(True) 63 | 64 | # Get the Germany polygon. Make sure to plot the full layer before setting the 65 | # filter, or you'll only plot Germany (or you could clear the filter and then 66 | # plot). 67 | ds = ogr.Open(os.path.join(data_dir, 'global')) 68 | country_lyr = ds.GetLayer('ne_50m_admin_0_countries') 69 | vp.plot(country_lyr, fill=False) 70 | country_lyr.SetAttributeFilter('name = "Germany"') 71 | feat = country_lyr.GetNextFeature() 72 | germany = feat.geometry().Clone() 73 | 74 | # Plot world cities as yellow dots. 75 | city_lyr = ds.GetLayer('ne_50m_populated_places') 76 | city_lyr.GetFeatureCount() 77 | vp.plot(city_lyr, 'y.') 78 | 79 | # Use the Germany polygon to set a spatial filter and draw the result as blue 80 | # circles. 81 | city_lyr.SetSpatialFilter(germany) 82 | city_lyr.GetFeatureCount() 83 | vp.plot(city_lyr, 'bo') 84 | 85 | # Add an attribute filter to find the cities with a population over 1,000,000 86 | # and draw them as red squares. Since the spatial filter is still in effect, 87 | # you should only get large cities in Germany. 88 | city_lyr.SetAttributeFilter('pop_min > 1000000') 89 | city_lyr.GetFeatureCount() 90 | vp.plot(city_lyr, 'rs') 91 | 92 | # Remove the spatial filter so now you get global cities with a population 93 | # over 1,000,000. Draw the results as magenta triangles. 94 | city_lyr.SetSpatialFilter(None) 95 | city_lyr.GetFeatureCount() 96 | vp.plot(city_lyr, 'm^', markersize=8) 97 | 98 | # Clear the plot and then replot country outlines. 99 | vp.clear() 100 | country_lyr.SetAttributeFilter(None) 101 | vp.plot(country_lyr, fill=False) 102 | 103 | # Set a spatial filter using bounding coordinates and draw the result in yellow. 104 | country_lyr.SetSpatialFilterRect(110, -50, 160, 10) 105 | vp.plot(country_lyr, 'y') 106 | 107 | 108 | 109 | ######################## To clone or not to clone? ########################### 110 | 111 | # Get a sample layer and the first feature from it. 112 | ds = ogr.Open(os.path.join(data_dir, 'global')) 113 | lyr = ds.GetLayer('ne_50m_admin_0_countries') 114 | feat = lyr.GetNextFeature() 115 | 116 | # Now get the feature's geometry and also a clone of that geometry. 117 | geom = feat.geometry() 118 | geom_clone = feat.geometry().Clone() 119 | 120 | # Set the feat variable to a new feature, so the original one is no longer 121 | # accessible. 122 | feat = lyr.GetNextFeature() 123 | 124 | # Try to get the area of the cloned polygon. This should work just fine. 125 | print(geom_clone.GetArea()) 126 | 127 | # Try to get the area of the original polygon. This should cause Python to 128 | # crash because the polygon is linked to the feature that is no longer 129 | # available. 130 | print(geom.GetArea()) 131 | 132 | # Here are some more examples that would all cause Python to crash for similar 133 | # reasons. 134 | fn = os.path.join(data_dir, 'global') 135 | 136 | # Neither the data source or layer are stored in memory. 137 | feat = ogr.Open(fn, 0).GetLayer(0).GetNextFeature() 138 | 139 | # The data source is not stored in memory. 140 | lyr = ogr.Open(fn, 0).GetLayer(0) 141 | feat = lyr.GetNextFeature() 142 | 143 | # The data source is deleted from memory 144 | ds = ogr.Open(fn, 0) 145 | lyr = ds.GetLayer(0) 146 | del ds 147 | feat = lyr.GetNextFeature() 148 | 149 | 150 | 151 | ###################### 5.3 Using SQL to create temp layers ################# 152 | 153 | # Use the OGR SQL dialect and a shapefile to order the world's countries by 154 | # population in descending order and print out the first three (which will be 155 | # the ones with the highest population since you used descending order). 156 | ds = ogr.Open(os.path.join(data_dir, 'global')) 157 | sql = '''SELECT ogr_geom_area as area, name, pop_est 158 | FROM 'ne_50m_admin_0_countries' ORDER BY POP_EST DESC''' 159 | lyr = ds.ExecuteSQL(sql) 160 | pb.print_attributes(lyr, 3) 161 | 162 | # Use the SQLite dialect and a SQLite database to do the same thing, but now 163 | # you can limit the result set itself to three records. This will only work if 164 | # you have SQLite support. 165 | ds = ogr.Open(os.path.join(data_dir, 'global', 166 | 'natural_earth_50m.sqlite')) 167 | sql = '''SELECT geometry, area(geometry) AS area, name, pop_est 168 | FROM countries ORDER BY pop_est DESC LIMIT 3''' 169 | lyr = ds.ExecuteSQL(sql) 170 | pb.print_attributes(lyr) 171 | 172 | # Join the populated places and country shapefiles together so that you can 173 | # see information about cities but also the countries that they're in, at the 174 | # same time. This uses the default OGR dialect. 175 | ds = ogr.Open(os.path.join(data_dir, 'global')) 176 | sql = '''SELECT pp.name AS city, pp.pop_min AS city_pop, 177 | c.name AS country, c.pop_est AS country_pop 178 | FROM ne_50m_populated_places pp 179 | LEFT JOIN ne_50m_admin_0_countries c 180 | ON pp.adm0_a3 = c.adm0_a3 181 | WHERE pp.adm0cap = 1''' 182 | lyr = ds.ExecuteSQL(sql) 183 | pb.print_attributes(lyr, 3, geom=False) 184 | 185 | # Try plotting the results to see that it returns cities, not countries. 186 | vp = VectorPlotter(True) 187 | vp.plot(lyr) 188 | 189 | # You can also compute data on the fly. Multiplying pp.pop_min by 1.0 turns 190 | # it into a float so that the math works. Otherwise it does integer math and 191 | # percent is 0. Instead of '1.0 * pp.pop_min', you could also do something like 192 | # 'CAST(pp.pop_min AS float(10))'. This example is not in the text. 193 | ds = ogr.Open(os.path.join(data_dir, 'global')) 194 | sql = '''SELECT pp.name AS city, pp.pop_min AS city_pop, 195 | c.name AS country, c.pop_est AS country_pop, 196 | 1.0 * pp.pop_min / c.pop_est * 100 AS percent 197 | FROM ne_50m_populated_places pp 198 | LEFT JOIN ne_50m_admin_0_countries c 199 | ON pp.adm0_a3 = c.adm0_a3 200 | WHERE pp.adm0cap = 1''' 201 | lyr = ds.ExecuteSQL(sql) 202 | pb.print_attributes(lyr, 3, geom=False) 203 | 204 | # Join two shapefiles again, but this time use the SQLite dialect. Now you can 205 | # also include fields from the secondary (countries) table in the WHERE clause. 206 | # You could also include a LIMIT if you wanted. 207 | ds = ogr.Open(os.path.join(data_dir, 'global')) 208 | sql = '''SELECT pp.name AS city, pp.pop_min AS city_pop, 209 | c.name AS country, c.pop_est AS country_pop 210 | FROM ne_50m_populated_places pp 211 | LEFT JOIN ne_50m_admin_0_countries c 212 | ON pp.adm0_a3 = c.adm0_a3 213 | WHERE pp.adm0cap = 1 AND c.continent = "South America"''' 214 | lyr = ds.ExecuteSQL(sql, dialect='SQLite') 215 | pb.print_attributes(lyr, 3) 216 | 217 | # Plot the counties in California. 218 | ds = ogr.Open(os.path.join(data_dir, 'US')) 219 | sql = 'SELECT * FROM countyp010 WHERE state = "CA"' 220 | lyr = ds.ExecuteSQL(sql) 221 | vp = VectorPlotter(True) 222 | vp.plot(lyr, fill=False) 223 | 224 | # Union the counties together and plot the result. This will only work if you 225 | # have SpatiaLite support. 226 | sql = 'SELECT st_union(geometry) FROM countyp010 WHERE state = "CA"' 227 | lyr = ds.ExecuteSQL(sql, dialect='SQLite') 228 | vp.plot(lyr, 'w') 229 | 230 | # Do the same with PostGIS, but only if you've set up a PostGIS server and 231 | # loaded your data in. 232 | conn_str = 'PG:host=localhost user=chrisg password=mypass dbname=geodata' 233 | ds = ogr.Open(conn_str) 234 | sql = "SELECT st_union(geom) FROM us.counties WHERE state = 'CA'" 235 | lyr = ds.ExecuteSQL(sql) 236 | vp.plot(lyr) 237 | 238 | 239 | 240 | ##################### 5.4 Taking advantage of filters ###################### 241 | 242 | # Use a filter and CopyLayer to easily copy all capital cities to a new 243 | # shapefile. 244 | ds = ogr.Open(os.path.join(data_dir, 'global'), 1) 245 | 246 | # Open the global cities shapefile and set a filter for capital cities. 247 | in_lyr = ds.GetLayer('ne_50m_populated_places') 248 | in_lyr.SetAttributeFilter("FEATURECLA = 'Admin-0 capital'") 249 | 250 | # Copy the filtered layer to a new shapefile. 251 | out_lyr = ds.CopyLayer(in_lyr, 'capital_cities2') 252 | out_lyr.SyncToDisk() 253 | 254 | 255 | # Use ExecuteSQL to pull out just a few attributes and copy that to a new 256 | # shapefile. 257 | sql = """SELECT NAME, ADM0NAME FROM ne_50m_populated_places 258 | WHERE FEATURECLA = 'Admin-0 capital'""" 259 | in_lyr2 = ds.ExecuteSQL(sql) 260 | out_lyr2 = ds.CopyLayer(in_lyr2, 'capital_cities3') 261 | out_lyr2.SyncToDisk() 262 | 263 | del ds 264 | -------------------------------------------------------------------------------- /Chapter7/chapter7.py: -------------------------------------------------------------------------------- 1 | import os 2 | from osgeo import ogr 3 | from ospybook.vectorplotter import VectorPlotter 4 | 5 | 6 | # Set this variable to your osgeopy-data directory so that the following 7 | # examples will work without editing. We'll use the os.path.join() function 8 | # to combine this directory and the filenames to make a complete path. Of 9 | # course, you can type the full path to the file for each example if you'd 10 | # prefer. 11 | data_dir = r'D:\osgeopy-data' 12 | # data_dir = 13 | 14 | 15 | 16 | ########################## 7.1 Overlay tools ############################### 17 | 18 | # Look at New Orleans wetlands. First get a specific marsh feature near New 19 | # Orleans. 20 | vp = VectorPlotter(True) 21 | water_ds = ogr.Open(os.path.join(data_dir, 'US', 'wtrbdyp010.shp')) 22 | water_lyr = water_ds.GetLayer(0) 23 | water_lyr.SetAttributeFilter('WaterbdyID = 1011327') 24 | marsh_feat = water_lyr.GetNextFeature() 25 | marsh_geom = marsh_feat.geometry().Clone() 26 | vp.plot(marsh_geom, 'b') 27 | 28 | # Get the New Orleans boundary. 29 | nola_ds = ogr.Open(os.path.join(data_dir, 'Louisiana', 'NOLA.shp')) 30 | nola_lyr = nola_ds.GetLayer(0) 31 | nola_feat = nola_lyr.GetNextFeature() 32 | nola_geom = nola_feat.geometry().Clone() 33 | vp.plot(nola_geom, fill=False, ec='red', ls='dashed', lw=3) 34 | 35 | # Intersect the marsh and boundary polygons to get the part of the marsh that 36 | # falls within New Orleans city boundaries. 37 | intersection = marsh_geom.Intersection(nola_geom) 38 | vp.plot(intersection, 'yellow', hatch='x') 39 | 40 | # Figure out how much of New Orleans is wetlands. Throw out lakes and anything 41 | # not in the vicinity of New Orleans, and then loop through the remaining water 42 | # body features. For each one, find the area of the feature that is contained 43 | # within city boundaries and add it to a running total. Then it's easy to 44 | # figure the percentage by dividing that total by the area of New Orleans. 45 | water_lyr.SetAttributeFilter("Feature != 'Lake'") 46 | water_lyr.SetSpatialFilter(nola_geom) 47 | wetlands_area = 0 48 | for feat in water_lyr: 49 | intersect = feat.geometry().Intersection(nola_geom) 50 | wetlands_area += intersect.GetArea() 51 | pcnt = wetlands_area / nola_geom.GetArea() 52 | print('{:.1%} of New Orleans is wetland'.format(pcnt)) 53 | 54 | # Another way to figure out how much of New Orleans is wetlands, this time 55 | # using layers instead of individual geometries. You need to set the attribute 56 | # filter, but a spatial filter isn't necessary. In this case you'll need an 57 | # empty layer to store the intersection results in, so create a temporary one 58 | # in memory. Then run the intersection and use SQL to sum up the areas. 59 | water_lyr.SetSpatialFilter(None) 60 | water_lyr.SetAttributeFilter("Feature != 'Lake'") 61 | 62 | memory_driver = ogr.GetDriverByName('Memory') 63 | temp_ds = memory_driver.CreateDataSource('temp') 64 | temp_lyr = temp_ds.CreateLayer('temp') 65 | 66 | nola_lyr.Intersection(water_lyr, temp_lyr) 67 | 68 | sql = 'SELECT SUM(OGR_GEOM_AREA) AS area FROM temp' 69 | lyr = temp_ds.ExecuteSQL(sql) 70 | pcnt = lyr.GetFeature(0).GetField('area') / nola_geom.GetArea() 71 | print('{:.1%} of New Orleans is wetland'.format(pcnt)) 72 | 73 | 74 | ######################### 7.2 Proximity tools ############################## 75 | 76 | # Open layers for examples. 77 | shp_ds = ogr.Open(os.path.join(data_dir, 'US')) 78 | volcano_lyr = shp_ds.GetLayer('us_volcanos_albers') 79 | cities_lyr = shp_ds.GetLayer('cities_albers') 80 | 81 | # Find out how far Seattle is from Mount Rainier. 82 | volcano_lyr.SetAttributeFilter("NAME = 'Rainier'") 83 | feat = volcano_lyr.GetNextFeature() 84 | rainier = feat.geometry().Clone() 85 | 86 | cities_lyr.SetSpatialFilter(None) 87 | cities_lyr.SetAttributeFilter("NAME = 'Seattle'") 88 | feat = cities_lyr.GetNextFeature() 89 | seattle = feat.geometry().Clone() 90 | 91 | meters = round(rainier.Distance(seattle)) 92 | miles = meters / 1600 93 | print('{} meters ({} miles)'.format(meters, miles)) 94 | 95 | 96 | 97 | ############################# 2.5D Geometries ################################ 98 | 99 | # Take a look at the distance between two 2D points. The distance should be 4. 100 | pt1_2d = ogr.Geometry(ogr.wkbPoint) 101 | pt1_2d.AddPoint(15, 15) 102 | pt2_2d = ogr.Geometry(ogr.wkbPoint) 103 | pt2_2d.AddPoint(15, 19) 104 | print(pt1_2d.Distance(pt2_2d)) 105 | 106 | # Now create some 2.5D points, using the same x and y coordinates, but adding 107 | # z coordinates. The distance now, if three dimensions were taken into account, 108 | # would be 5. But ogr still returns 4. This is because the z coordinates are 109 | # ignored. 110 | pt1_25d = ogr.Geometry(ogr.wkbPoint25D) 111 | pt1_25d.AddPoint(15, 15, 0) 112 | pt2_25d = ogr.Geometry(ogr.wkbPoint25D) 113 | pt2_25d.AddPoint(15, 19, 3) 114 | print(pt1_25d.Distance(pt2_25d)) 115 | 116 | # Take a look at the area of a 2D polygon. The area should be 100. 117 | ring = ogr.Geometry(ogr.wkbLinearRing) 118 | ring.AddPoint(10, 10) 119 | ring.AddPoint(10, 20) 120 | ring.AddPoint(20, 20) 121 | ring.AddPoint(20, 10) 122 | poly_2d = ogr.Geometry(ogr.wkbPolygon) 123 | poly_2d.AddGeometry(ring) 124 | poly_2d.CloseRings() 125 | print(poly_2d.GetArea()) 126 | 127 | # Now create a 2.5D polygon, again using the same x and y coordinates, but 128 | # providing a z coordinate for a couple of the vertices. The area of this in 129 | # three dimensions is around 141, but ogr still returns 100. 130 | ring = ogr.Geometry(ogr.wkbLinearRing) 131 | ring.AddPoint(10, 10, 0) 132 | ring.AddPoint(10, 20, 0) 133 | ring.AddPoint(20, 20, 10) 134 | ring.AddPoint(20, 10, 10) 135 | poly_25d = ogr.Geometry(ogr.wkbPolygon25D) 136 | poly_25d.AddGeometry(ring) 137 | poly_25d.CloseRings() 138 | print(poly_25d.GetArea()) 139 | 140 | # If three dimensions were taken into account, pt1_d2 would be contained in the 141 | # 2D polygon, but not the 3D one. But since the 3D one is really 2.5D, ogr 142 | # thinks the point is contained in both polygons. 143 | print(poly_2d.Contains(pt1_2d)) 144 | print(poly_25d.Contains(pt1_2d)) 145 | 146 | 147 | ############################ 7.3 Wind farms ################################ 148 | 149 | # Open the census layer and add a field containing population per square 150 | # kilometer. 151 | census_fn = os.path.join(data_dir, 'California', 'ca_census_albers.shp') 152 | census_ds = ogr.Open(census_fn, True) 153 | census_lyr = census_ds.GetLayer() 154 | density_field = ogr.FieldDefn('popsqkm', ogr.OFTReal) 155 | census_lyr.CreateField(density_field) 156 | for row in census_lyr: 157 | pop = row.GetField('HD01_S001') 158 | sqkm = row.geometry().GetArea() / 1000000 159 | row.SetField('popsqkm', pop / sqkm) 160 | census_lyr.SetFeature(row) 161 | 162 | # Get the Imperial County geomtery. 163 | county_fn = os.path.join(data_dir, 'US', 'countyp010.shp') 164 | county_ds = ogr.Open(county_fn) 165 | county_lyr = county_ds.GetLayer() 166 | county_lyr.SetAttributeFilter("COUNTY ='Imperial County'") 167 | county_row = next(county_lyr) 168 | county_geom = county_row.geometry().Clone() 169 | del county_ds 170 | 171 | # Transform the county geometry to the same spatial reference as the census 172 | # data and then use it as a spatial filter on the census data. 173 | county_geom.TransformTo(census_lyr.GetSpatialRef()) 174 | census_lyr.SetSpatialFilter(county_geom) 175 | 176 | # Set an attribute filter based on the population field you created a minute 177 | # ago. 178 | census_lyr.SetAttributeFilter('popsqkm < 0.5') 179 | 180 | # Open the wind layer and select the polygons with a good enough wind rating. 181 | wind_fn = os.path.join(data_dir, 'California', 'california_50m_wind_albers.shp') 182 | wind_ds = ogr.Open(wind_fn) 183 | wind_lyr = wind_ds.GetLayer() 184 | wind_lyr.SetAttributeFilter('WPC >= 3') 185 | 186 | # Create a shapefile to hold the output data. 187 | out_fn = os.path.join(data_dir, 'California', 'wind_farm.shp') 188 | out_ds = ogr.GetDriverByName('ESRI Shapefile').CreateDataSource(out_fn) 189 | out_lyr = out_ds.CreateLayer('wind_farm', wind_lyr.GetSpatialRef(), ogr.wkbPolygon) 190 | out_lyr.CreateField(ogr.FieldDefn('wind', ogr.OFTInteger)) 191 | out_lyr.CreateField(ogr.FieldDefn('popsqkm', ogr.OFTReal)) 192 | out_row = ogr.Feature(out_lyr.GetLayerDefn()) 193 | 194 | # The following code is the same as listing 7.3. 195 | # Loop through the census rows and intersect the census geometry with the 196 | # county geometry and use that as a spatial filter on the wind data. 197 | for census_row in census_lyr: 198 | census_geom = census_row.geometry() 199 | census_geom = census_geom.Intersection(county_geom) 200 | wind_lyr.SetSpatialFilter(census_geom) 201 | 202 | print('Intersecting census tract with {0} wind polygons'.format( 203 | wind_lyr.GetFeatureCount())) 204 | 205 | # Only bother with adding new rows to the output if there are wind 206 | # polygons selected by the filters. 207 | if wind_lyr.GetFeatureCount() > 0: 208 | out_row.SetField('popsqkm', census_row.GetField('popsqkm')) 209 | for wind_row in wind_lyr: 210 | wind_geom = wind_row.geometry() 211 | 212 | # Again, only bother with adding rows to the output if there is 213 | # an intersection to add. 214 | if census_geom.Intersect(wind_geom): 215 | new_geom = census_geom.Intersection(wind_geom) 216 | out_row.SetField('wind', wind_row.GetField('WPC')) 217 | out_row.SetGeometry(new_geom) 218 | out_lyr.CreateFeature(out_row) 219 | del out_ds 220 | 221 | 222 | ########################## 7.3 Animal tracking ############################# 223 | 224 | # Delete the bad points from the output of listing 7.5. 225 | shp_fn = os.path.join(data_dir, 'Galapagos', 'albatross_dd.shp') 226 | shp_ds = ogr.Open(shp_fn, True) 227 | shp_lyr = shp_ds.GetLayer() 228 | shp_lyr.SetSpatialFilterRect(-1, -1, 1, 1) 229 | for shp_row in shp_lyr: 230 | shp_lyr.DeleteFeature(shp_row.GetFID()) 231 | shp_lyr.SetSpatialFilter(None) 232 | shp_ds.ExecuteSQL('REPACK ' + shp_lyr.GetName()) 233 | shp_ds.ExecuteSQL('RECOMPUTE EXTENT ON ' + shp_lyr.GetName()) 234 | del shp_ds 235 | 236 | # ogr2ogr command for projecting the shapefile. You need to run this from the 237 | # folder containing the shapefile. 238 | ogr2ogr -f "ESRI Shapefile" -t_srs "+proj=lcc +lat_1=-5 +lat_2=-42 +lat_0=-32 +lon_0=-60 +x_0=0 +y_0=0 +ellps=aust_SA +units=m +no_defs" albatross_lambert.shp albatross_dd.shp 239 | 240 | 241 | # Function to get unique values from an attribute field. This is in the 242 | # ch7funcs module. 243 | def get_unique(datasource, layer_name, field_name): 244 | sql = 'SELECT DISTINCT {0} FROM {1}'.format(field_name, layer_name) 245 | lyr = datasource.ExecuteSQL(sql) 246 | values = [] 247 | for row in lyr: 248 | values.append(row.GetField(field_name)) 249 | datasource.ReleaseResultSet(lyr) 250 | return values 251 | 252 | # Get the maximum distance between GPS fixes for each animal tag. 253 | ds = ogr.Open(os.path.join(data_dir, 'Galapagos')) 254 | for tag_id in get_unique(ds, 'albatross_lambert', 'tag_id'): 255 | sql = """SELECT MAX(distance) FROM albatross_lambert 256 | WHERE tag_id = '{0}'""".format(tag_id) 257 | lyr = ds.ExecuteSQL(sql) 258 | for row in lyr: 259 | print '{0}: {1}'.format(tag_id, row.GetField(0)) 260 | -------------------------------------------------------------------------------- /Chapter6/chapter6.py: -------------------------------------------------------------------------------- 1 | import os 2 | from osgeo import ogr 3 | from ospybook.vectorplotter import VectorPlotter 4 | 5 | 6 | # Set this variable to your osgeopy-data directory so that the following 7 | # examples will work without editing. We'll use the os.path.join() function 8 | # to combine this directory and the filenames to make a complete path. Of 9 | # course, you can type the full path to the file for each example if you'd 10 | # prefer. 11 | data_dir = r'D:\osgeopy-data' 12 | # data_dir = 13 | 14 | 15 | 16 | ######################### 6.2 Working with points ########################## 17 | 18 | ########################### 6.2.1 Single points ############################ 19 | 20 | # Create the firepit point. 21 | firepit = ogr.Geometry(ogr.wkbPoint) 22 | firepit.AddPoint(59.5, 11.5) 23 | 24 | # Try out GetX and GetY. 25 | x, y = firepit.GetX(), firepit.GetY() 26 | print('{}, {}'.format(x, y)) 27 | 28 | # Take a look at the point. 29 | print(firepit) 30 | vp = VectorPlotter(True) 31 | vp.plot(firepit, 'bo') 32 | 33 | # Edit the point coordinates. 34 | firepit.AddPoint(59.5, 13) 35 | vp.plot(firepit, 'rs') 36 | print(firepit) 37 | 38 | # Or edit the point using SetPoint instead of AddPoint. 39 | firepit.SetPoint(0, 59.5, 13) 40 | print(firepit) 41 | 42 | # Make a 2.5D point. 43 | firepit = ogr.Geometry(ogr.wkbPoint25D) 44 | firepit.AddPoint(59.5, 11.5, 2) 45 | print(firepit) 46 | 47 | 48 | 49 | ########################### 6.2.2 Multiple points ########################## 50 | 51 | # Create the multipoint to hold the water spigots. Create multipoint and point 52 | # geometries. For each spigot, edit the point coordinates and add the point to 53 | # the multipoint. 54 | faucets = ogr.Geometry(ogr.wkbMultiPoint) 55 | faucet = ogr.Geometry(ogr.wkbPoint) 56 | faucet.AddPoint(67.5, 16) 57 | faucets.AddGeometry(faucet) 58 | faucet.AddPoint(73, 31) 59 | faucets.AddGeometry(faucet) 60 | faucet.AddPoint(91, 24.5) 61 | faucets.AddGeometry(faucet) 62 | 63 | # Take a look at the multipoint. 64 | vp.clear() 65 | vp.plot(faucets, 'bo') 66 | vp.zoom(-5) 67 | print(faucets) 68 | 69 | # Edit the coordinates for the second faucet. 70 | faucets.GetGeometryRef(1).AddPoint(75, 32) 71 | vp.plot(faucets, 'k^', 'tmp') 72 | print(faucets) 73 | 74 | # Change the coordinates back for the next example. 75 | faucets.GetGeometryRef(1).AddPoint(73, 31) 76 | vp.remove('tmp') 77 | 78 | # Move all spigots two units to the east. After plotting, you will probably 79 | # have to zoom out a bit in order to really see what happened. 80 | for i in range(faucets.GetGeometryCount()): 81 | pt = faucets.GetGeometryRef(i) 82 | pt.AddPoint(pt.GetX() + 2, pt.GetY()) 83 | vp.plot(faucets, 'rs') 84 | vp.zoom(-5) 85 | 86 | 87 | 88 | ######################### 6.3 Working with lines ########################### 89 | 90 | ########################### 6.3.1 Single lines ############################# 91 | 92 | # Create the sidewalk line. Make sure to add the vertices in order. 93 | sidewalk = ogr.Geometry(ogr.wkbLineString) 94 | sidewalk.AddPoint(54, 37) 95 | sidewalk.AddPoint(62, 35.5) 96 | sidewalk.AddPoint(70.5, 38) 97 | sidewalk.AddPoint(74.5, 41.5) 98 | 99 | # Take a look at the line. 100 | vp = VectorPlotter(True) 101 | vp.plot(sidewalk, 'b-') 102 | print(sidewalk) 103 | 104 | # Change the last vertex. 105 | sidewalk.SetPoint(3, 76, 41.5) 106 | vp.plot(sidewalk, 'k--', 'tmp') 107 | print(sidewalk) 108 | 109 | # Change the coordinates back for the next example. 110 | sidewalk.SetPoint(3, 74.5, 41.5) 111 | vp.remove('tmp') 112 | 113 | # Move the line one unit to the north. 114 | for i in range(sidewalk.GetPointCount()): 115 | sidewalk.SetPoint(i, sidewalk.GetX(i), sidewalk.GetY(i) + 1) 116 | vp.plot(sidewalk, 'r--') 117 | print(sidewalk) 118 | 119 | # Try out GetGeometryCount to prove it that it returns zero for a single 120 | # geometry. 121 | print(sidewalk.GetPointCount()) # vertices 122 | print(sidewalk.GetGeometryCount()) # sub-geometries 123 | 124 | # Move the sidewalk back to its original location for the next example. 125 | for i in range(sidewalk.GetPointCount()): 126 | sidewalk.SetPoint(i, sidewalk.GetX(i), sidewalk.GetY(i) - 1) 127 | 128 | # Look at the list of tuples containing vertex coordinates. 129 | print(sidewalk.GetPoints()) 130 | 131 | # Insert a new vertex between the 2nd and 3rd vertices. 132 | vertices = sidewalk.GetPoints() 133 | vertices[2:2] = [(66.5, 35)] 134 | print(vertices) 135 | 136 | # Create a new line geometry from the list of vertices. 137 | new_sidewalk = ogr.Geometry(ogr.wkbLineString) 138 | for vertex in vertices: 139 | new_sidewalk.AddPoint(*vertex) 140 | vp.plot(new_sidewalk, 'g:') 141 | 142 | # Get the original line for the multiple vertices example. 143 | ds = ogr.Open(os.path.join(data_dir, 'misc', 'line-example.geojson')) 144 | lyr = ds.GetLayer() 145 | feature = lyr.GetFeature(0) 146 | line = feature.geometry().Clone() 147 | vp.clear() 148 | vp.plot(line, 'b-') 149 | 150 | # Add a bunch of vertices at different locations. Start from the end so that 151 | # earlier indices don't get messed up. 152 | vertices = line.GetPoints() 153 | vertices[26:26] = [(87, 57)] 154 | vertices[19:19] = [(95, 38), (97, 43), (101, 42)] 155 | vertices[11:11] = [(121, 18)] 156 | vertices[5:5] = [(67, 32), (74, 30)] 157 | new_line = ogr.Geometry(ogr.wkbLineString) 158 | for vertex in vertices: 159 | new_line.AddPoint(*vertex) 160 | vp.plot(new_line, 'b--') 161 | 162 | # Insert a vertex without creating a new line. 163 | vertices = sidewalk.GetPoints() 164 | vertices[2:2] = [(66.5, 35)] 165 | for i in range(len(vertices)): 166 | sidewalk.SetPoint(i, *vertices[i]) 167 | vp.plot(sidewalk, 'k-', lw=3) 168 | 169 | 170 | 171 | ########################### The Python *-operator ########################### 172 | 173 | pt = ogr.Geometry(ogr.wkbPoint) 174 | vertex = (10, 20) 175 | 176 | # Resolves to pt.AddPoint(10, 20), which works 177 | pt.AddPoint(*vertex) 178 | 179 | # Resolves to pt.AddPoint((10, 20)), which fails because only one thing 180 | # (a tuple) is getting passed to AddPoint. 181 | pt.AddPoint(vertex) 182 | 183 | 184 | 185 | ########################### 6.3.2 Multiple lines ########################### 186 | 187 | # Create the pathways multiline. Create three individual lines, one for each 188 | # path. Then add them all to the multiline geometry. 189 | path1 = ogr.Geometry(ogr.wkbLineString) 190 | path1.AddPoint(61.5, 29) 191 | path1.AddPoint(63, 20) 192 | path1.AddPoint(62.5, 16) 193 | path1.AddPoint(60, 13) 194 | 195 | path2 = ogr.Geometry(ogr.wkbLineString) 196 | path2.AddPoint(60.5, 12) 197 | path2.AddPoint(68.5, 13.5) 198 | 199 | path3 = ogr.Geometry(ogr.wkbLineString) 200 | path3.AddPoint(69.5, 33) 201 | path3.AddPoint(80, 33) 202 | path3.AddPoint(86.5, 22.5) 203 | 204 | paths = ogr.Geometry(ogr.wkbMultiLineString) 205 | paths.AddGeometry(path1) 206 | paths.AddGeometry(path2) 207 | paths.AddGeometry(path3) 208 | 209 | # Take a look at the multiline. 210 | vp.clear() 211 | vp.plot(paths, 'b-') 212 | print(paths) 213 | 214 | # Edit the second vertex in the first path. 215 | paths.GetGeometryRef(0).SetPoint(1, 63, 22) 216 | vp.plot(paths, 'k--', 'tmp') 217 | print(paths) 218 | 219 | # Change the coordinates back for the next example. 220 | paths.GetGeometryRef(0).SetPoint(1, 63, 20) 221 | vp.remove('tmp') 222 | 223 | # Move the line two units east and three south. Get each individual path from 224 | # the multipath with GetGeometryRef, and then edit the vertices for the path. 225 | for i in range(paths.GetGeometryCount()): 226 | path = paths.GetGeometryRef(i) 227 | for j in range(path.GetPointCount()): 228 | path.SetPoint(j, path.GetX(j) + 2, path.GetY(j) - 3) 229 | vp.plot(paths, 'r--') 230 | 231 | 232 | 233 | ######################### 6.4 Working with polygons ######################## 234 | 235 | ######################### 6.4.1 Single polygons ############################ 236 | 237 | # Make the yard boundary polygon. Create a ring and add the vertices in order, 238 | # and then add the ring to the polygon. 239 | ring = ogr.Geometry(ogr.wkbLinearRing) 240 | ring.AddPoint(58, 38.5) 241 | ring.AddPoint(53, 6) 242 | ring.AddPoint(99.5, 19) 243 | ring.AddPoint(73, 42) 244 | yard = ogr.Geometry(ogr.wkbPolygon) 245 | yard.AddGeometry(ring) 246 | yard.CloseRings() 247 | 248 | # Take a look at the polygon. Setting fill=False makes the polygon hollow when 249 | # it is drawn. 250 | vp = VectorPlotter(True) 251 | vp.plot(yard, fill=False, edgecolor='blue') 252 | print(yard) 253 | 254 | # Move the polygon five units west, by moving the ring. 255 | ring = yard.GetGeometryRef(0) 256 | for i in range(ring.GetPointCount()): 257 | ring.SetPoint(i, ring.GetX(i) - 5, ring.GetY(i)) 258 | vp.plot(yard, fill=False, ec='red', linestyle='dashed') 259 | 260 | # Move the yard back to its original location for the next example. 261 | ring = yard.GetGeometryRef(0) 262 | for i in range(ring.GetPointCount()): 263 | ring.SetPoint(i, ring.GetX(i) + 5, ring.GetY(i)) 264 | 265 | # Cut off one of the sharp corners by replacing the third vertex with two other 266 | # vertices. 267 | ring = yard.GetGeometryRef(0) 268 | vertices = ring.GetPoints() 269 | vertices[2:3] = ((90, 16), (90, 27)) 270 | for i in range(len(vertices)): 271 | ring.SetPoint(i, *vertices[i]) 272 | vp.plot(yard, fill=False, ec='black', ls='dotted', linewidth=3) 273 | 274 | 275 | ######################### 6.4.2 Multiple polygons ########################## 276 | 277 | # Make the garden boxes multipolygon. Create a regular polygon for each raised 278 | # bed, and then add it to the multipolygon. This is the code in listing 6.3. 279 | box1 = ogr.Geometry(ogr.wkbLinearRing) 280 | box1.AddPoint(87.5, 25.5) 281 | box1.AddPoint(89, 25.5) 282 | box1.AddPoint(89, 24) 283 | box1.AddPoint(87.5, 24) 284 | garden1 = ogr.Geometry(ogr.wkbPolygon) 285 | garden1.AddGeometry(box1) 286 | 287 | box2 = ogr.Geometry(ogr.wkbLinearRing) 288 | box2.AddPoint(89, 23) 289 | box2.AddPoint(92, 23) 290 | box2.AddPoint(92,22) 291 | box2.AddPoint(89,22) 292 | garden2 = ogr.Geometry(ogr.wkbPolygon) 293 | garden2.AddGeometry(box2) 294 | 295 | gardens = ogr.Geometry(ogr.wkbMultiPolygon) 296 | gardens.AddGeometry(garden1) 297 | gardens.AddGeometry(garden2) 298 | gardens.CloseRings() 299 | 300 | # Take a look at the multipolygon. 301 | vp.clear() 302 | vp.plot(gardens, fill=False, ec='blue') 303 | vp.zoom(-1) 304 | print(gardens) 305 | 306 | # Move the garden boxes on unit east and half a unit north. For each polygon 307 | # contained in the multipolygon, get the ring and edit its vertices. 308 | for i in range(gardens.GetGeometryCount()): 309 | ring = gardens.GetGeometryRef(i).GetGeometryRef(0) 310 | for j in range(ring.GetPointCount()): 311 | ring.SetPoint(j, ring.GetX(j) + 1, ring.GetY(j) + 0.5) 312 | vp.plot(gardens, fill=False, ec='red', ls='dashed') 313 | vp.zoom(-1) 314 | 315 | 316 | ######################### 6.4.3 Polygons with holes ######################### 317 | 318 | # Make the yard multipolygon, but this time cut out a hole for the house. 319 | # Make the outer boundary ring first (it's the same as earlier). Then make the 320 | # ring for the house (hole). Then create a polygon and add the rings, making 321 | # sure to add the outer boundary first. Close all of the rings at once by 322 | # calling CloseRings *after* adding all of the rings. This is the code from 323 | # listing 6.4. 324 | lot = ogr.Geometry(ogr.wkbLinearRing) 325 | lot.AddPoint(58, 38.5) 326 | lot.AddPoint(53, 6) 327 | lot.AddPoint(99.5, 19) 328 | lot.AddPoint(73, 42) 329 | 330 | house = ogr.Geometry(ogr.wkbLinearRing) 331 | house.AddPoint(67.5, 29) 332 | house.AddPoint(69, 25.5) 333 | house.AddPoint(64, 23) 334 | house.AddPoint(69, 15) 335 | house.AddPoint(82.5, 22) 336 | house.AddPoint(76, 31.5) 337 | 338 | yard = ogr.Geometry(ogr.wkbPolygon) 339 | yard.AddGeometry(lot) 340 | yard.AddGeometry(house) 341 | yard.CloseRings() 342 | 343 | # Take a look at the polygon. 344 | vp.clear() 345 | vp.plot(yard, 'yellow') 346 | print(yard) 347 | 348 | # Move the yard five units south. Get each ring the same way you would get an 349 | # inner polygon from a multipolygon. 350 | for i in range(yard.GetGeometryCount()): 351 | ring = yard.GetGeometryRef(i) 352 | for j in range(ring.GetPointCount()): 353 | ring.SetPoint(j, ring.GetX(j) - 5, ring.GetY(j)) 354 | vp.plot(yard, fill=False, hatch='x', color='blue') 355 | -------------------------------------------------------------------------------- /Chapter10/chapter10.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import numpy as np 4 | from osgeo import gdal, osr 5 | 6 | 7 | # Set this variable to your osgeopy-data directory so that the following 8 | # examples will work without editing. We'll use the os.path.join() function 9 | # to combine this directory and the filenames to make a complete path. Of 10 | # course, you can type the full path to the file for each example if you'd 11 | # prefer. 12 | data_dir = r'D:\osgeopy-data' 13 | # data_dir = 14 | 15 | 16 | 17 | ############################## 10.1 Ground control points #################### 18 | 19 | # See listing10_1.py, since the example reuses some code from that listing. 20 | 21 | 22 | ############# 10.2 Converting pixel coordinates to another image ############# 23 | 24 | # Create a function to get the extent of a raster and try it out on the 25 | # raster just created in listing 10.1. This function is in 26 | def get_extent(fn): 27 | '''Returns min_x, max_y, max_x, min_y''' 28 | ds = gdal.Open(fn) 29 | gt = ds.GetGeoTransform() 30 | return (gt[0], gt[3], gt[0] + gt[1] * ds.RasterXSize, 31 | gt[3] + gt[5] * ds.RasterYSize) 32 | 33 | # The raster with GCPs doesn't have a geotransform so this extent isn't 34 | # correct. 35 | os.chdir(os.path.join(data_dir, 'Utah')) 36 | print(get_extent('cache.tif')) 37 | 38 | # But this one is. 39 | print(get_extent('cache2.tif')) 40 | 41 | # Extra examples... 42 | # Remember the vashon.tif file created in the last chapter? Let's use it 43 | # for a transformer example. 44 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 45 | vashon_ds = gdal.Open('vashon.tif') 46 | full_ds = gdal.Open('nat_color.tif') 47 | 48 | # Create a transformer that will map pixel coordinates from the Vashon 49 | # dataset into the full one. 50 | trans = gdal.Transformer(vashon_ds, full_ds, []) 51 | 52 | # Use the transformer to figure out the pixel offsets in the full image 53 | # that correspond with the upper left corner of the vashon one. 54 | success, xyz = trans.TransformPoint(False, 0, 0) 55 | print(success, xyz) 56 | 57 | # If we use the output from that and go the reverse direction, we'll get the 58 | # upper left corner for vashon. 59 | success, xyz = trans.TransformPoint(True, 6606, 3753) 60 | print(success, xyz) 61 | 62 | 63 | 64 | ############################## 10.3 Color tables ############################# 65 | 66 | # Make a copy of the raster we just created in listing 10.3. 67 | os.chdir(os.path.join(data_dir, 'Switzerland')) 68 | original_ds = gdal.Open('dem_class2.tif') 69 | ds = original_ds.GetDriver().CreateCopy('dem_class3.tif', original_ds) 70 | 71 | # Get the existing color table from the band. 72 | band = ds.GetRasterBand(1) 73 | colors = band.GetRasterColorTable() 74 | 75 | # Change the entry for 5. 76 | colors.SetColorEntry(5, (250, 250, 250)) 77 | 78 | # Set the modified color table back on the raster. 79 | band.SetRasterColorTable(colors) 80 | del band, ds 81 | 82 | 83 | 84 | ############################# 10.3.1 Transparency ############################ 85 | 86 | # Let's take the output from listing 10.3 and add some transparency. We have 87 | # to make a copy of the dataset, though, so we can add the alpha band. 88 | os.chdir(os.path.join(data_dir, 'Switzerland')) 89 | original_ds = gdal.Open('dem_class2.tif') 90 | driver = gdal.GetDriverByName('gtiff') 91 | 92 | # This is the only line shown in the text. The rest of the copy code is 93 | # left out for space reasons (and because you know all about it by now). 94 | ds = driver.Create('dem_class4.tif', original_ds.RasterXSize, 95 | original_ds.RasterYSize, 2, gdal.GDT_Byte, ['ALPHA=YES']) 96 | 97 | # Add the projection and and geotransform info to the copy. 98 | ds.SetProjection(original_ds.GetProjection()) 99 | ds.SetGeoTransform(original_ds.GetGeoTransform()) 100 | 101 | # Read the data in from dem_class2. 102 | original_band1 = original_ds.GetRasterBand(1) 103 | data = original_band1.ReadAsArray() 104 | 105 | # Write the data to band 1 of the new raster and copy the color table over. 106 | band1 = ds.GetRasterBand(1) 107 | band1.WriteArray(data) 108 | band1.SetRasterColorTable(original_band1.GetRasterColorTable()) 109 | band1.SetRasterColorInterpretation(gdal.GCI_PaletteIndex) 110 | band1.SetNoDataValue(original_band1.GetNoDataValue()) 111 | 112 | ds.FlushCache() 113 | 114 | 115 | # Now that we're finally done copying, go back to the text and find 116 | # everywhere in the data array that the pixel value is 5 and set 117 | # the value to 65 instead. Set everything else to 255. 118 | import numpy as np 119 | data = band1.ReadAsArray() 120 | data = np.where(data == 5, 65, 255) 121 | 122 | # Now write the modified data array to the second (alpha) band in the new 123 | # raster. 124 | band2 = ds.GetRasterBand(2) 125 | band2.WriteArray(data) 126 | band2.SetRasterColorInterpretation(gdal.GCI_AlphaBand) 127 | 128 | del ds, original_ds 129 | 130 | # To get this output to render correctly in QGIS, you might have to change 131 | # the symbology. Open up the Layer Properties dialog, go to the Style tab, 132 | # and choose Pletted for the Render type. Try putting the swiss_dem raster 133 | # under it so you can see the transparency. 134 | 135 | 136 | 137 | ############################### 10.4 Histograms ############################## 138 | 139 | # Look at approximate vs exact histogram values. 140 | os.chdir(os.path.join(data_dir, 'Switzerland')) 141 | ds = gdal.Open('dem_class2.tif') 142 | band = ds.GetRasterBand(1) 143 | approximate_hist = band.GetHistogram() 144 | exact_hist = band.GetHistogram(approx_ok=False) 145 | print('Approximate:', approximate_hist[:7], sum(approximate_hist)) 146 | print('Exact:', exact_hist[:7], sum(exact_hist)) 147 | 148 | # Look at the current default histogram. 149 | print(band.GetDefaultHistogram()) 150 | 151 | # Change the default histogram so that it lumps 1 & 2, 3 & 4, and leaves 5 152 | # by itself. 153 | hist = band.GetHistogram(0.5, 6.5, 3, approx_ok=False) 154 | band.SetDefaultHistogram(1, 6, hist) 155 | 156 | # Look at what the default histogram is now. 157 | print(band.GetDefaultHistogram()) 158 | 159 | # Get the individual bits of data from the default histogram. 160 | min_val, max_val, n, hist = band.GetDefaultHistogram() 161 | print(min_val, max_val, n) 162 | print(hist) 163 | 164 | 165 | 166 | ################################## 10.6 VRTs ################################# 167 | 168 | # XML defining a VRT band. It uses the first band in whatever dataset it's 169 | # pointed to with the SourceFilename tag. 170 | xml = ''' 171 | 172 | {0} 173 | 1 174 | 175 | ''' 176 | 177 | # Create a 3-band VRT with the same dimensions as one of the Landsat bands. 178 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 179 | tmp_ds = gdal.Open('p047r027_7t20000730_z10_nn30.tif') 180 | driver = gdal.GetDriverByName('vrt') 181 | ds = driver.Create('nat_color.vrt', tmp_ds.RasterXSize, 182 | tmp_ds.RasterYSize, 3) 183 | ds.SetProjection(tmp_ds.GetProjection()) 184 | ds.SetGeoTransform(tmp_ds.GetGeoTransform()) 185 | 186 | # Point the VRT to the 3 individual GeoTIFFs holding the Landsat bands. 187 | # The bands are stored in the VRT in the order you add them, so we add 188 | # them in 3,2,1 order so that we get RGB. 189 | metadata = {'source_0': xml.format('p047r027_7t20000730_z10_nn30.tif')} 190 | ds.GetRasterBand(1).SetMetadata(metadata, 'vrt_sources') 191 | 192 | metadata = {'source_0': xml.format('p047r027_7t20000730_z10_nn20.tif')} 193 | ds.GetRasterBand(2).SetMetadata(metadata, 'vrt_sources') 194 | 195 | metadata = {'source_0': xml.format('p047r027_7t20000730_z10_nn10.tif')} 196 | ds.GetRasterBand(3).SetMetadata(metadata, 'vrt_sources') 197 | 198 | del ds, tmp_ds 199 | 200 | 201 | ########################## 10.6.2 Troublesome formats ######################## 202 | 203 | # Use the VRT created in listing 9.5 to create a jpeg of Vashon Island. 204 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 205 | ds = gdal.Open('vashon.vrt') 206 | gdal.GetDriverByName('jpeg').CreateCopy('vashon.jpg', ds) 207 | 208 | 209 | 210 | ########################## 10.6.3 Reprojecting images ######################## 211 | 212 | # Reproject the nat_color.tif from UTM to unprojected lat/lon. First create 213 | # the output SRS. 214 | srs = osr.SpatialReference() 215 | srs.SetWellKnownGeogCS('WGS84') 216 | 217 | # Open the nat_color file. 218 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 219 | old_ds = gdal.Open('nat_color.tif') 220 | 221 | # Create a VRT in memory that does the reproject. 222 | vrt_ds = gdal.AutoCreateWarpedVRT(old_ds, None, srs.ExportToWkt(), 223 | gdal.GRA_Bilinear) 224 | 225 | # Copy the VRT to a GeoTIFF so we have a file on disk. 226 | gdal.GetDriverByName('gtiff').CreateCopy('nat_color_wgs84.tif', vrt_ds) 227 | 228 | 229 | 230 | ########################### 10.7 Callback functions ########################## 231 | 232 | # Let's calculate statistics on the natural color Landsat image and show 233 | # progress while it does it (this image probably already has stats, so this 234 | # will go really fast). Watch your output window to see what happens. 235 | os.chdir(os.path.join(data_dir, 'Landsat', 'Washington')) 236 | ds = gdal.Open('nat_color.tif') 237 | for i in range(ds.RasterCount): 238 | ds.GetRasterBand(i + 1).ComputeStatistics(False, gdal.TermProgress_nocb) 239 | 240 | 241 | # How about using the gdal callback function with my own stuff? Let's just 242 | # list all of the files in the current diretory and pretend to do something 243 | # with them. 244 | def process_file(fn): 245 | # Slow things down a bit by counting to 1,000,000 for each file. 246 | for i in range(1000000): 247 | pass # do nothing 248 | 249 | list_of_files = os.listdir('.') 250 | for i in range(len(list_of_files)): 251 | process_file(list_of_files[i]) 252 | gdal.TermProgress_nocb(i / float(len(list_of_files))) 253 | gdal.TermProgress_nocb(100) 254 | 255 | 256 | 257 | ###################### 10.8 Exceptions and error handlers #################### 258 | 259 | os.chdir(os.path.join(data_dir, 'Switzerland')) 260 | 261 | # This will fail because the second filename has an extra f at the end. The 262 | # first one is the only one that will get statistics calculated. 263 | file_list = ['dem_class.tif', 'dem_class2.tiff', 'dem_class3.tif'] 264 | for fn in file_list: 265 | ds = gdal.Open(fn) 266 | ds.GetRasterBand(1).ComputeStatistics(False) 267 | 268 | # You could check to see if the file could be opened and skip it if not. 269 | for fn in file_list: 270 | ds = gdal.Open(fn) 271 | if ds is None: 272 | print('Could not compute stats for ' + fn) 273 | else: 274 | print('Computing stats for ' + fn) 275 | ds.GetRasterBand(1).ComputeStatistics(False) 276 | 277 | # Or you could use exceptions and a try/except block. 278 | gdal.UseExceptions() 279 | for fn in file_list: 280 | try: 281 | ds = gdal.Open(fn) 282 | ds.GetRasterBand(1).ComputeStatistics(False) 283 | except: 284 | print('Could not compute stats for ' + fn) 285 | # Uncomment this if you also want it to print the gdal error message. 286 | # print(gdal.GetLastErrorMsg()) 287 | 288 | # Turn exceptions off. 289 | gdal.DontUseExceptions() 290 | 291 | # How about the second example, but without the gdal error message? 292 | gdal.PushErrorHandler('CPLQuietErrorHandler') 293 | for fn in file_list: 294 | ds = gdal.Open(fn) 295 | if ds is None: 296 | print('Could not compute stats for ' + fn) 297 | else: 298 | print('Computing stats for ' + fn) 299 | ds.GetRasterBand(1).ComputeStatistics(False) 300 | 301 | # Get the default error handler back. 302 | gdal.PopErrorHandler() 303 | 304 | # Call the error function yourself. 305 | def do_something(ds1, ds2): 306 | if ds1.GetProjection() != ds2.GetProjection(): 307 | gdal.Error(gdal.CE_Failure, gdal.CPLE_AppDefined, 308 | 'Datasets must have the same SRS') 309 | return False 310 | # now do your stuff 311 | ds1 = gdal.Open(os.path.join(data_dir, 'Switzerland', 'dem_class.tif')) 312 | ds2 = gdal.Open(os.path.join(data_dir, 'Landsat', 'Washington', 'nat_color.tif')) 313 | do_something(ds1, ds2) 314 | del ds1, ds2 315 | 316 | # Create your own error handler. 317 | import ospybook as pb 318 | def log_error_handler(err_class, err_no, msg): 319 | logging.error('{} - {}: {}'.format( 320 | pb.get_gdal_constant_name('CE', err_class), 321 | pb.get_gdal_constant_name('CPLE', err_no), 322 | msg)) 323 | 324 | # Use your custom error handler to print messages to the screen. 325 | import logging 326 | gdal.PushErrorHandler(log_error_handler) 327 | ds1 = gdal.Open(os.path.join(data_dir, 'Switzerland', 'dem_class.tif')) 328 | ds2 = gdal.Open(os.path.join(data_dir, 'Landsat', 'Washington', 'nat_color.tif')) 329 | do_something(ds1, ds2) 330 | del ds1, ds2 331 | 332 | # Or use it to write to a file (change to file path!). 333 | import logging 334 | logging.basicConfig(filename='d:/temp/log.txt') 335 | gdal.PushErrorHandler(log_error_handler) 336 | ds1 = gdal.Open(os.path.join(data_dir, 'Switzerland', 'dem_class.tif')) 337 | ds2 = gdal.Open(os.path.join(data_dir, 'Landsat', 'Washington', 'nat_color.tif')) 338 | do_something(ds1, ds2) 339 | del ds1, ds2 340 | 341 | 342 | 343 | ###################### Get constant names with ospybook ##################### 344 | 345 | import ospybook as pb 346 | 347 | # Get the GDT constant that has a value of 5. 348 | print(pb.get_gdal_constant_name('GDT', 5)) 349 | --------------------------------------------------------------------------------