aboutsummaryrefslogtreecommitdiff
path: root/docs/plot/plot.py
blob: 2ccb2814bdf2e73c44ce9fd0beb1eb5000b1e035 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
'''
Plot map data in different projections supported by proj.4

Can be called either as

> python plot.py definitions.txt [out]

or

> python plotproj.py "+proj=lcc +lat_1=20" [out]

The output dir is optional. Uses current dir if not set.

Change PROJ and COASTLINE_DATA to path on your local system
before running the script.
'''

import os
import sys
import json
import subprocess

import numpy as np
import matplotlib.pyplot as plt

PROJ = 'proj'

COASTLINE_DATA = 'data/coastline.geojson'

GRATICULE_WIDTH = 15
N_POINTS = 100

LAT_MIN = -90
LAT_MAX = 90
LON_MIN = -180
LON_MAX = 180

def project(coordinates, proj_string, in_radians=False):
    '''
    Project geographical coordinates

    Input:
    ------
        coordinates:        numpy ndarray of size (N,2) and type double.
                            longitude, latitude
        proj_string:        Definition of output projection

    Out:
    ----
        numpy ndarray with shape (N,2) with N pairs of 2D
        coordinates (x, y).
    '''

    # proj expects binary input to be in radians
    if not in_radians:
        coordinates = np.deg2rad(coordinates)

    # set up cmd call. -b for binary in/out
    args = [PROJ, '-b']
    args.extend(proj_string.split(' '))

    proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    stdout, _ = proc.communicate(coordinates.tobytes())

    out = np.frombuffer(stdout, dtype=np.double)
    return np.reshape(out, coordinates.shape)

def meridian(lon, lat_min, lat_max):
    '''
    Calculate meridian coordinates.
    '''
    coords = np.zeros((N_POINTS, 2))
    coords[:, 0] = lon
    coords[:, 1] = np.linspace(lat_min, lat_max, N_POINTS)
    return coords

def parallel(lat, lon_min, lon_max):
    '''
    Calculate parallel coordinates.
    '''
    coords = np.zeros((N_POINTS, 2))
    coords[:, 0] = np.linspace(lon_min, lon_max, N_POINTS)
    coords[:, 1] = lat
    return coords

def plotproj(proj_str, coastline, frame, graticule, outdir):
    '''
    Plot map.
    '''
    fig = plt.figure()
    axes = fig.add_subplot(111)

    # Plot frame
    for line in frame:
        line = project(line, proj_str)
        x, y = zip(*line)
        plt.plot(x, y, '-k')

    # Plot coastline
    for feature in coastline['features']:
        C = np.array(feature['geometry']['coordinates'])

        if np.any(C[:, 0] > 180.0):
            C[C[:, 0] > 180.0, 0] = 180.0

        if np.any(C[:, 0] < -180.0):
            C[C[:, 0] < -180.0, 0] = -180.0

        if np.any(C[:, 1] > 90.0):
            C[C[:, 1] > 90.0, 1] = 90.0

        if np.any(C[:, 1] < -90.0):
            C[C[:, 1] < -90.0, 1] = -90.0

        coords = project(C, proj_str)
        x, y = zip(*coords)
        plt.plot(x, y, '-k', linewidth=0.5)

    # Plot graticule
    graticule = project(graticule, proj_str)
    for feature in graticule:
        x, y = zip(*feature)
        plt.plot(x, y, '-k', linewidth=0.2)

    axes.axis('off')
    font = {'family': 'serif',
            'color': 'black',
            'style': 'italic',
            'size': 12}
    plt.suptitle(proj_str, fontdict=font)

    plt.autoscale(tight=True)
    name = proj_str.split()[0].split('=')[1]
    plt.savefig(outdir + '/' + name + '.png', dpi=300)

    # Clean up
    fig = None
    del fig
    axes = None
    del axes
    plt.close()

if __name__ == "__main__":
    '''
    '''

    with open(COASTLINE_DATA) as data:
        coastline = json.load(data)

    if os.path.exists(sys.argv[1]):
        # it must be a file with proj-strings
        with open(sys.argv[1]) as projs:
            projs = projs.readlines()
    else:
        #assumes it is a single roj string
        projs = [sys.argv[1]]

    outdir = '.'
    if len(sys.argv) > 2:
        outdir = sys.argv[2]

    frame = []
    frame.append(parallel(LAT_MIN, LON_MIN, LON_MAX))
    frame.append(parallel(LAT_MAX, LON_MIN, LON_MAX))

    # build graticule
    graticule = []
    for lon in range(LON_MIN, LON_MAX+1, GRATICULE_WIDTH):
        graticule.append(meridian(lon, LAT_MIN, LAT_MAX))

    for lat in range(LAT_MIN, LAT_MAX+1, GRATICULE_WIDTH):
        graticule.append(parallel(lat, LON_MIN, LON_MAX))

    for i, proj in enumerate(projs):
        proj_str = proj.strip()
        print(i, proj_str)
        plotproj(proj_str, coastline, frame, graticule, outdir)