Pycoast

Pycoast is a Python package to add coastlines, borders and rivers to raster images using data from the GSHHS and WDBII datasets

_images/euro_coast_agg.png

Installation

Pycoast depends on pyshp and PIL.

Install pycoast and dependencies.

Download the zipped GSHHS and WDBII Shapefiles from SOEST. Unzip the files to a data directory (hereafter called GSHHS_DATA_ROOT). The structure of GSHHS_DATA_ROOT should now be:

.
├── GSHHS_shp
│   ├── c
│   ├── f
│   ├── h
│   ├── i
│   └── l
└── WDBII_shp
    ├── c
    ├── f
    ├── h
    ├── i
    └── l

Where each dir on the lowest level contains Shapefiles like GSHHS_shp/c/GSHHS_c_L1.shp

Usage

Pycoast can be used to add coastlines, borders and rivers to a raster image if the geographic projection of the image and the image extent in projection coordinates are known

_images/BMNG_clouds_201109181715_areaT2.png

Pycoast can add contours to either a PIL image object:

>>> from PIL import Image
>>> from pycoast import ContourWriter
>>> img = Image.open('BMNG_clouds_201109181715_areaT2.png')
>>> proj4_string = '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
>>> area_extent = (-3363403.31,-2291879.85,2630596.69,2203620.1)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriter('/home/esn/data/gshhs')
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> cw.add_rivers(img, area_def, level=5, outline='blue')
>>> cw.add_borders(img, area_def, outline=(255, 0, 0))
>>> img.show()

or to an image file:

>>> from pycoast import ContourWriter
>>> proj4_string = '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
>>> area_extent = (-3363403.31,-2291879.85,2630596.69,2203620.1)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriter('/home/esn/data/gshhs')
>>> cw.add_coastlines_to_file('BMNG_clouds_201109181715_areaT2.png', area_def, resolution='l', level=4)
>>> cw.add_rivers_to_file('BMNG_clouds_201109181715_areaT2.png', area_def, level=5, outline='blue')
>>> cw.add_borders_to_file('BMNG_clouds_201109181715_areaT2.png', area_def, outline=(255, 0, 0))

Where the area_extent is the extent of the image in projection coordinates as (x_ll, y_ll, x_ur, x_ur) measured at pixel edges.

The argument to ContourWriter must be replaced with your GSHHS_DATA_ROOT.

_images/euro_coast.png

The resulting (not so pretty) image shows the effect of the various arguments. The resolution keyword argument controls the resolution of the dataset used. It defaults to ‘c’ for coarse. Increasing the resolution also increases the processing time. The level keyword argument controls the detail level of the dataset used. It defaults to 1 for the lowest detail level.

Instead of a tuple for area_def a pyresample AreaDefinition object can be used.

See method docstrings for information about possible argument values see method docstrings.

Creating an image with coastlines only:

>>> from PIL import Image
>>> from pycoast import ContourWriter
>>> img = Image.new('RGB', (425, 425))
>>> proj4_string = '+proj=geos +lon_0=0.0 +a=6378169.00 +b=6356583.80 +h=35785831.0'
>>> area_extent = (-5570248.4773392612, -5567248.074173444, 5567248.074173444, 5570248.4773392612)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriter('/home/esn/data/gshhs')
>>> cw.add_coastlines(img, area_def, resolution='l')
>>> img.show()
_images/geos_coast.png

High quality contours using AGG

The default plotting mode of pycoast uses PIL for rendering of contours. PIL does not support antialiasing and opacity. The AGG engine can be used for making high quality images using the aggdraw module.

First install the aggdraw module.

Tip: if the building of aggdraw fails with:

agg_array.h:523: error: cast from ‘agg::int8u*’ to ‘unsigned int’ loses precision

Try:

export CFLAGS="-fpermissive"

before building.

Using pycoast with AGG for making antialiased drawing:

>>> from PIL import Image
>>> from pycoast import ContourWriterAGG
>>> img = Image.new('RGB', (425, 425))
>>> proj4_string = '+proj=geos +lon_0=0.0 +a=6378169.00 +b=6356583.80 +h=35785831.0'
>>> area_extent = (-5570248.4773392612, -5567248.074173444, 5567248.074173444, 5570248.4773392612)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG('/home/esn/data/gshhs')
>>> cw.add_coastlines(img, (proj4_string, area_extent), resolution='l', width=0.5)
>>> img.show()
_images/geos_coast_agg.png

and making the not-so-nice image from the first example nice:

>>> from PIL import Image
>>> from pycoast import ContourWriterAGG
>>> img = Image.open('BMNG_clouds_201109181715_areaT2.png')
>>> proj4_string = '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
>>> area_extent = (-3363403.31,-2291879.85,2630596.69,2203620.1)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG('/home/esn/data/gshhs')
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> cw.add_rivers(img, area_def, level=5, outline='blue', width=0.5, outline_opacity=127)
>>> cw.add_borders(img, area_def, outline=(255, 0, 0), width=3, outline_opacity=32)
>>> img.show()
_images/euro_coast_agg.png

See docstrings of ContourWriterAGG methods for argument descriptions.

Adding graticule to images

Pycoast can be used to add graticule to images. For PIL:

>>> from PIL import Image, ImageFont
>>> from pycoast import ContourWriter
>>> proj4_string = '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
>>> area_extent = (-3363403.31,-2291879.85,2630596.69,2203620.1)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriter('/home/esn/data/gshhs')
>>> font = ImageFont.truetype("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf",16)
>>> img = Image.open('BMNG_clouds_201109181715_areaT2.png')
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> cw.add_grid(img, area_def, (10.0,10.0),(2.0,2.0), font,fill='blue',
...             outline='blue', minor_outline='blue')
>>> img.show()
_images/euro_grid.png

The font argument is optional for PIL if it is not given a default font will be used.

and for AGG:

>>> from PIL import Image, ImageFont
>>> from pycoast import ContourWriterAGG
>>> import aggdraw
>>> proj4_string = '+proj=stere +lon_0=8.00 +lat_0=50.00 +lat_ts=50.00 +ellps=WGS84'
>>> area_extent = (-3363403.31,-2291879.85,2630596.69,2203620.1)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG('/home/esn/data/gshhs')
>>> font = aggdraw.Font('black', '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf',
...                     opacity=127, size=16)
>>> img = Image.open('BMNG_clouds_201109181715_areaT2.png')
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> cw.add_grid(img, area_def, (10.0,10.0),(2.0,2.0),font,
...             outline='blue',outline_opacity=175,width=1.0,
...             minor_outline='lightblue',minor_outline_opacity=200,minor_width=0.5,
...             minor_is_tick=False)
>>> img.show()
_images/euro_grid_agg.png

Note the difference in the optional font argument for PIL and AGG. With AGG the font argument is mandatory unless the keyword argument write_text=False is used.

From v0.5.0 the graticule is also usable for globe projections:

>>> from PIL import Image
>>> from pycoast import ContourWriterAGG
>>> img = Image.new('RGB', (425, 425))
>>> proj4_string = '+proj=geos +lon_0=0.0 +a=6378169.00 +b=6356583.80 +h=35785831.0'
>>> area_extent = (-5570248.4773392612, -5567248.074173444, 5567248.074173444, 5570248.4773392612)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG(gshhs_root_dir)
>>> cw.add_coastlines(img, area_def, resolution='l')
>>> cw.add_grid(img, area_def, (10.0,10.0),(2.0,2.0), fill='blue',
... outline='blue', minor_outline='blue', write_text=False)
>>> img.show()
_images/grid_geos_agg.png

The lon and lat labeling is shown where the lines intersect the image border. By default the lon intersections with top and bottom and the lat intersections with left and right border are displayed . The placement behaviour can be controlled with the lon_placement and lat_placement keyword variables. The placement specifier is a string containing the desired placement where ‘t’ is top, ‘b’ bottom, ‘l’ left and ‘r’ right. E.g. lon_placement='tl' will make the lon labels display at the top and left border.

>>> from PIL import Image
>>> from pycoast import ContourWriterAGG
>>> import aggdraw
>>> img = Image.new('RGB', (425, 425))
>>> proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
>>> area_extent = (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG('/home/esn/data/gshhs')
>>> cw.add_coastlines(img, area_def, resolution='c', level=4)
>>> font = aggdraw.Font('blue', '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf', size=10)
>>> cw.add_grid(img, area_def, (10.0,10.0),(2.0,2.0), font=font, fill='blue',
...             outline='blue', minor_outline='blue',
...             lon_placement='tblr', lat_placement='')
>>> img.show()
_images/nh_grid_coarse_agg.png

Tip: If the adding graticule with AGG fails with something like:

Traceback (most recent call last):
    File "grid_demo_AGG.py", line 13, in <module>
        font=aggdraw.Font("blue", "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf", size=16)
IOError: cannot load font (no text renderer)

make sure the FREETYPE_ROOT in setup.py of aggdraw points to the correct location e.g. set FREETYPE_ROOT = “/usr”

Pycoast from a configuration file

If you want to run to avoid typing the same options over and over again, of if caching is an optimization you want, you can use a configuration file with the pycoast options you need:

[cache]
file=/var/run/satellit/white_overlay

[coasts]
level=1
width=0.75
outline=white
fill=yellow

[borders]
outline=white
width=0.5

Then, you can just call:

>>> cw.add_overlay_from_config("my_config.cfg", area_def)

Custom shapes and lines

Pycoast can add custom polygons and lines, useful for outlining special target areas. The following example shows how we might use the add_polygon method to highlight the Reykjavik Air Traffic Control area and an additional filled box around Iceland.

>>> from PIL import Image
>>> from pycoast import ContourWriterAGG
>>> img = Image.new('RGB', (600, 600))
>>> proj4_string = '+proj=laea +lat_0=90 +lon_0=0 +a=6371228.0 +units=m'
>>> area_extent = (-5326849.0625, -5326849.0625, 5326849.0625, 5326849.0625)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG('/home/esn/data/gshhs')
...
>>> REYKJAVIK_ATC = [(0.0,73.0),(0.0,61.0),(-30.0,61.0),(-39,63.5),(-55+4/6.0,63.5),(-57+45/60.0,65),(-76,76),(-75,78),(-60,82),(0,90),(30,82),(0,82)]
>>> ICELAND_BOX = [(-25,62.5),(-25,67),(-13,67),(-13,62.5)]
>>> cw.add_polygon(img, area_def, REYKJAVIK_ATC, outline='red',width=2)
>>> cw.add_polygon(img, area_def, ICELAND_BOX, outline='green', fill='gray', width=2)
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> img.show()
_images/nh_polygons_agg.png

The add_polygon method accepts a list of longitude, latitude pairs. An equivalent add_line method is also available which does not tie the first and last coordinates in the list.

Now we can plot some air traffic routes from Keflavik to Seattle, Moscow and Beijing,

>>> ROUTE_KEF_MOS = [(-22.6056, 63.985), (-19.046655824698217, 64.2936159845089), (-15.41883293246517, 64.51404924194419), (-11.744200494490052, 64.64399069686961), (-8.046778033221322, 64.6820416591038), (-4.351563677581442, 64.62778714494442), (-0.6834599011921236, 64.48181810544278), (2.9337905930008565, 64.24569983825512), (6.478548138904879, 63.92189044240429), (9.932010650466118, 63.513618932636106), (13.278688573156892, 63.02473642018875), (16.506610526365268, 62.459555054119136), (19.607285620724404, 61.82268835291907), (22.575472462848946, 61.118903806204194), (25.408815405909454, 60.352995069199515), (28.107407514323345, 59.52967751291583), (30.673330797710015, 58.65350788682086), (33.110211639277665, 57.7288266642078), (35.42281629953696, 56.75972029885026), (37.6167, 55.75)]
>>> ROUTE_KEF_SEA = [(-22.6056, 63.985), (-28.15308892820336, 65.36580325755281), (-34.26244035327647, 66.52172028653052), (-40.896187287785146, 67.41807846160079), (-47.960443294166176, 68.02301075853937), (-55.302469834902446, 68.31206181696378), (-62.72513195737088, 68.27259499211274), (-70.01742497152813, 67.90637421611629), (-76.99054572503543, 67.22919099479928), (-83.50520476774184, 66.26770704836584), (-89.48175180569157, 65.05485573003652), (-94.89452260904564, 63.62539374850556), (-99.75771059724035, 62.012611982850714), (-104.1099689970044, 60.24644267746881), (-108.00184199066507, 58.352707879886715), (-111.48717146239099, 56.3531052759957), (-114.61800147728289, 54.26558085318135), (-117.4419933502085, 52.104852107803715), (-120.00142613885524, 49.88294778482337), (-122.3331, 47.6097)]
>>> ROUTE_KEF_BEI = [(-22.6056, 63.985), (-17.489150553128045, 67.07686353046147), (-10.93541135202904, 69.95803521761742), (-2.422591560170639, 72.52376059083646), (8.601530816977142, 74.6151942209109), (22.350514164676376, 76.01770036199035), (38.03768523094268, 76.51449133498859), (53.7147372147881, 76.00872266593849), (67.44042282956654, 74.598879606615), (78.43970951791597, 72.50222030140003), (86.9320528199369, 69.93299364768527), (93.47049967796295, 67.04949777818322), (98.57586637530908, 63.95606630048991), (102.64426083795271, 60.71933633909033), (105.95716114438707, 57.38212969906091), (108.71149093382456, 53.97256160920469), (111.04582088648519, 50.509589240989264), (113.05910256207024, 47.00634823698568), (114.82328673157406, 43.472181706860376), (116.3917, 39.9139)]
>>> cw.add_line(img, area_def, ROUTE_KEF_MOS, outline='yellow',outline_opacity=180,width=3)
>>> cw.add_line(img, area_def, ROUTE_KEF_SEA, outline='yellow',outline_opacity=180,width=3)
>>> cw.add_line(img, area_def, ROUTE_KEF_BEI, outline='yellow',outline_opacity=180,width=3)
_images/nh_polygons_lines_agg.png

Shapes from ESRI shape files

Pycoast supports plotting of polygons and polylines from ESRI shapefiles, currently only in lonlat coordinates format. In the following example we use add_shapefile_shapes method to plot all line shapes found in the file Metareas.shp. We then use the add_shapefile_shape (notice the singular) to plot only the 3rd and 4th shape_id within the file BR_Regioes.shp.

>>> from pycoast import ContourWriter
>>> img = Image.new('RGB', (600, 600))
>>> proj4_string = '+proj=merc +lon_0=-60 +lat_ts=-30.0 +a=6371228.0 +units=m'
>>> area_extent = (-2000000.0, -5000000.0, 5000000.0, 2000000.0)
>>> area_def = (proj4_string, area_extent)
>>> cw = ContourWriterAGG(gshhs_root_dir)
...
>>> cw.add_coastlines(img, area_def, resolution='l', level=4)
>>> cw.add_shapefile_shapes(img, area_def,
                            'test/test_data/shapes/Metareas.shp',
                            outline='red',width=2)
>>> cw.add_shapefile_shape(img, area_def,
                            'test/test_data/shapes/divisao_politica/BR_Regioes.shp',
                            3, outline='blue')
>>> cw.add_shapefile_shape(img, area_def,
                            'test/test_data/shapes/divisao_politica/BR_Regioes.shp',
                            4, outline='blue', fill='green')
_images/brazil_shapefiles_agg.png

If your shapefile is not in lonlat coordinates, then you can re-project your shape file using ogr2ogr (part of GDAL tools), e.g.

$ ogr2ogr original.shp  lonlat.shp  -t_srs "+proj=latlong"

This should work if you have all the extra meta-files original.* included with your original.shp. Please refer to the OGR documentation for more information.

Testing

The tests can be run using nosetest:

$ cd <pycoast_dir>
$ nosetests tests/

The pycoast API

Pycoast