Skip to content
Snippets Groups Projects
  • Lionel GUEZ's avatar
    b55a165b
    Add TAGS rule in `CMakeLists.txt`. Do not rely on external · b55a165b
    Lionel GUEZ authored
    `settings.mk` in GNUmakefile.
    
    Bug fix in procedure `dispatch_snapshot`: an isolated eddy is a valid eddy.
    
    In script `plot_snapshot.py`, in function snaphot, replace argument ax
    with argument `new_figure` so the user does not have to call figure
    and `axes(projection = projection)`. Add argument light to function
    snapshot so we can call snapshot with light set to False for a second
    snapshot. Add a test to only read `h.nc` when we need it.
    
    In script `read_overlap.py`, print more information: number of nodes,
    number of edges, number of nodes with at least one successor, number
    of nodes with at least one predecessor, splitting events, merging
    events.
    
    In script `stat.py`, use the convention that shapefiles are grouped in
    a directory instead of identifying a set of shapefiles by the end of
    their basename.
    
    In main program `test_successive_overlap`, we know that `delta_in` for
    snapshot 1 and `delta_out` for snapshot 2 are `huge(0)` so do not test
    this. Print eddy identifiers in a single line. Add the printing of
    identifiers of isolated eddies.
    b55a165b
    History
    Add TAGS rule in `CMakeLists.txt`. Do not rely on external
    Lionel GUEZ authored
    `settings.mk` in GNUmakefile.
    
    Bug fix in procedure `dispatch_snapshot`: an isolated eddy is a valid eddy.
    
    In script `plot_snapshot.py`, in function snaphot, replace argument ax
    with argument `new_figure` so the user does not have to call figure
    and `axes(projection = projection)`. Add argument light to function
    snapshot so we can call snapshot with light set to False for a second
    snapshot. Add a test to only read `h.nc` when we need it.
    
    In script `read_overlap.py`, print more information: number of nodes,
    number of edges, number of nodes with at least one successor, number
    of nodes with at least one predecessor, splitting events, merging
    events.
    
    In script `stat.py`, use the convention that shapefiles are grouped in
    a directory instead of identifying a set of shapefiles by the end of
    their basename.
    
    In main program `test_successive_overlap`, we know that `delta_in` for
    snapshot 1 and `delta_out` for snapshot 2 are `huge(0)` so do not test
    this. Print eddy identifiers in a single line. Add the printing of
    identifiers of isolated eddies.
plot_snapshot.py 7.45 KiB
#!/usr/bin/env python3

"""Plots outermost contours and max-speed contours.

Red for anti-cyclones, blue for cyclones. 

Without the --light option. Empty circles for extrema
with a valid outermost contour, empty squares for other
extrema. Squares on outermost contour for a well-defined but invalid
outermost contour. Crosses on outermost contour for a valid outermost
contour but with no distinct max-speed contour. Filled circles on
outermost contour and max-speed contour for a valid outermost contour
with a distinct max-speed contour.

With the --light option. Crosses for extrema with a valid outermost
contour.

This scripts takes about 30 s of CPU for a 90° by 90° window, about 4
mn for a 180° by 180° window.

"""

import shapefile
import numpy as np
import matplotlib.pyplot as plt
import netCDF4
import math
import itertools
import cartopy.crs as ccrs
from os import path

def snaphot(directory, dashed = False, light = False, new_figure = False):
    """Plots extrema, outermost contours and max-speed contours.

    directory: containing the three shapefiles.
    dashed: boolean."""
    
    reader_extr = shapefile.Reader(path.join(directory, "extremum"))
    reader_outer = shapefile.Reader(path.join(directory, "outermost_contour"))

    try:
        reader_m_s = shapefile.Reader(path.join(directory, "max_speed_contour"))
    except shapefile.ShapefileException:
        print("Shapefile max_speed_contour not found. "
              "Max-speed contours will not be plotted.")
        m_s_iterShapes = itertools.repeat(None)
    else:
        m_s_iterShapes = reader_m_s.iterShapes()

    if new_figure:
        plt.figure()
        ax = plt.axes(projection = projection)
    else:
        ax = plt.gca()

    for shape_rec_extr, shape_outer, shape_m_s \
        in zip(reader_extr, reader_outer.iterShapes(), m_s_iterShapes):
        points = shape_rec_extr.shape.points[0]

        if args.window:
            points[0] += math.ceil((llcrnrlon - points[0]) / 360) * 360
            # (in [llcrnrlon, llcrnrlon + 2 pi[)

        if not args.window or (points[0] <= urcrnrlon 
                              and llcrnrlat <= points[1] <= urcrnrlat):
            if shape_rec_extr.record.cyclone == 0:
                    # Anti-cyclone
                color = "red"
            else:
                color = "blue"

            lines = ax.plot(points[0], points[1], markersize = 10,
                            color = color, fillstyle = "none",
                            transform = src_crs)

            if shape_rec_extr.record.valid == 1:
                if light:
                    lines[0].set_marker("+")
                else:
                    lines[0].set_marker("o")
            elif not light:
                # Invalid outermost contour
                lines[0].set_marker("s")

            if not light:
                ax.annotate(str(shape_rec_extr.record.eddy_index),
                            projection.transform_point(points[0], points[1],
                                                       src_crs),
                            xytext = (3, 3), textcoords = "offset points")

            if shape_outer.shapeType != shapefile.NULL and not light \
               or shape_rec_extr.record.valid:
                points = np.array(shape_outer.points)
                lines = ax.plot(points[:, 0], points[:, 1], color = color,
                                transform = src_crs)

                if not light:
                    if shape_rec_extr.record.valid == 0:
                        # Invalid outermost contour
                        lines[0].set_marker("s")
                        lines[0].set_fillstyle("none")
                    elif shape_m_s == None \
                         or shape_m_s.shapeType == shapefile.NULL:
                        lines[0].set_marker("x")
                    else:
                        lines[0].set_marker("o")

                if dashed: lines[0].set_linestyle("dashed")

                if shape_m_s != None and shape_m_s.shapeType != shapefile.NULL:
                    points = np.array(shape_m_s.points)

                    if shape_rec_extr.record.cyclone == 0:
                        # Anti-cyclone
                        color = "magenta"
                    else:
                        color = "cyan"

                    lines = ax.plot(points[:, 0], points[:, 1], color = color,
                                    transform = src_crs)
                    if not light: lines[0].set_marker("o")
                    if dashed: lines[0].set_linestyle("dashed")

    ax.gridlines(draw_labels = True)
    ax.coastlines()                  

if __name__ == "__main__":
    import argparse
    import sys
    
    parser = argparse.ArgumentParser()
    parser.add_argument("-v", "--velocity", help = "plot velocity field",
                        action = "store_true")
    parser.add_argument("-s", "--scale", default = 20, type = float,
                        help = "scale of arrows for the velocity field")
    parser.add_argument("-g", "--grid", help = "plot grid",
                        action = "store_true")
    parser.add_argument("-w", "--window", help = "choose a limited plot window",
                        action = "store_true")
    parser.add_argument("-l", "--light", help = "lighter plot",
                        action = "store_true")
    parser.add_argument("-d", "--dashed", action = "store_true",
                        help = "dashed linestyle, useful for a second snapshot")
    parser.add_argument("directory", help = "containing the three shapefiles")
    args = parser.parse_args()

    if args.grid or args.velocity:
        with netCDF4.Dataset("h.nc") as f:
            longitude = f.variables["longitude"][:]
            latitude = f.variables["latitude"][:]
    
    if args.window:
        l = input("llcrnrlon, llcrnrlat, urcrnrlon, "
                  "urcrnrlat = ? ").split(sep = ",")
        llcrnrlon, llcrnrlat, urcrnrlon, urcrnrlat = [float(v) for v in l]

        if urcrnrlon - llcrnrlon > 360:
            sys.exit("bad values of urcrnrlon and llcrnrlon")

        if args.grid or args.velocity:
            longitude += np.ceil((llcrnrlon - longitude) / 360) * 360
            # (in [llcrnrlon, llcrnrlon + 2 pi[)

            lon_mask = longitude <= urcrnrlon
            lat_mask = np.logical_and(latitude >= llcrnrlat,
                                      latitude <= urcrnrlat)
    elif args.grid or args.velocity:
        lon_mask = np.ones(len(longitude), dtype = bool)
        lat_mask = np.ones(len(latitude), dtype = bool)

    plt.figure()
    src_crs = ccrs.PlateCarree()
    projection = ccrs.PlateCarree()
    ##projection = ccrs.NorthPolarStereo()
    ax = plt.axes(projection = projection)

    if args.grid:
        lon_2d, lat_2d = np.meshgrid(longitude[lon_mask], latitude[lat_mask])
        ax.plot(lon_2d.reshape(-1), lat_2d.reshape(-1), transform = src_crs,
                marker = "+", color = "gray", linestyle = "None")

    snaphot(args.directory, args.dashed, args.light)

    if args.velocity:
        with netCDF4.Dataset("uv.nc") as f:
            quiver_return = ax.quiver(longitude[lon_mask], latitude[lat_mask],
                                      f["ugos"][0, lat_mask][:, lon_mask],
                                      f["vgos"][0, lat_mask][:, lon_mask],
                                      scale = args.scale, scale_units = "width",
                                      transform = src_crs)
        plt.quiverkey(quiver_return, 0.9, 0.9, 1, r"1 m s$^{-1}$",
                      coordinates = "figure")

    plt.show()