Prominent British Mountains

metadata

In a former life I considered myself a mountaineer; recently I stumbled across The Database of British and Irish Hills (DoBIH) and it got me thinking about how many of them I have visited. In the UK a “mountain” is anything with an elevation over 600m. Below is a map of all of the mountains in the British Isles.

Download:
  1. 512 px × 384 px (0.2 Mpx; 93.5 KiB)
  2. 1,024 px × 768 px (0.8 Mpx; 308.5 KiB)
  3. 2,048 px × 1,536 px (3.1 Mpx; 950.9 KiB)
  4. 2,880 px × 2,160 px (6.2 Mpx; 1.2 MiB)

As you can see, the vast majority of them are in Scotland.

Below is a map of all of the mountains over 900m in the British Isles.

Download:
  1. 512 px × 384 px (0.2 Mpx; 84.7 KiB)
  2. 1,024 px × 768 px (0.8 Mpx; 279.5 KiB)
  3. 2,048 px × 1,536 px (3.1 Mpx; 860.4 KiB)
  4. 2,880 px × 2,160 px (6.2 Mpx; 1.1 MiB)

Once again, the vast majority are in Scotland. Furthermore, the only ones in England are in The Lake District.

Recently, I have become interested in the idea of the prominence of mountains as a metric for is it worth climbing? Wikipedia has a list of all of the mountains in the British Isles with a prominence over 600m. Using The Database of British and Irish Hills (DoBIH) I wrote a Python script to create a map of these prominent mountains, which is shown below.

Download:
  1. 512 px × 384 px (0.2 Mpx; 86.4 KiB)
  2. 1,024 px × 768 px (0.8 Mpx; 280.9 KiB)
  3. 2,048 px × 1,536 px (3.1 Mpx; 857.9 KiB)
  4. 2,880 px × 2,160 px (6.2 Mpx; 1.1 MiB)

As you can see, adding a prominence filter on top of the elevation filter really starts to curtail the list of mountains in the British Isles: there are only 7 in Wales; and there are only 4 in England.

Finally, to be really strict, I created a map and table (shown below) of all of the mountains in the British Isles with a prominence over 900m.

Download:
  1. 512 px × 384 px (0.2 Mpx; 81.5 KiB)
  2. 1,024 px × 768 px (0.8 Mpx; 269.9 KiB)
  3. 2,048 px × 1,536 px (3.1 Mpx; 830.9 KiB)
  4. 2,880 px × 2,160 px (6.2 Mpx; 1.1 MiB)
Mountain Elevation [m] Prominence [m]
Ben More 1,174.0 986.0
Ben Lawers 1,214.0 915.0
Ben Nevis 1,344.5 1,344.5
Ben Macdui 1,309.0 950.0
Carn Eighe 1,182.8 1,147.0
Liathach - Spidean a’ Choire Leith 1,054.8 957.0
Sgurr Mor 1,108.9 914.0
Sgurr Alasdair 992.0 992.0
Ben More 966.0 966.0
Snowdon - Yr Wyddfa 1,085.0 1,039.0
Scafell Pike 978.1 912.0
Carrauntoohil 1,038.6 1,038.6
Brandon Mountain 951.7 927.0

This seems like a decent “bucket list” doesn’t it?

Below you will find the four Python scripts that I wrote to generate these four maps, enjoy!

  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
178
179
180
181
182
183
184
185
186
187
188
189

#!/usr/bin/env python3

# Use the proper idiom in the main module ...
# NOTE: See https://docs.python.org/3.12/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
if __name__ == "__main__":
    # NOTE: "The Database of British and Irish Hills" can be visited here:
    #         * http://www.hills-database.co.uk/
    #       ... and the links to download the database can be found here:
    #         * http://www.hills-database.co.uk/downloads.html

    # Import standard modules ...
    import csv
    import json
    import os
    import zipfile

    # Import special modules ...
    try:
        import cartopy
        cartopy.config.update(
            {
                "cache_dir" : os.path.expanduser("~/.local/share/cartopy_cache"),
            }
        )
    except:
        raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
    try:
        import matplotlib
        matplotlib.rcParams.update(
            {
                       "backend" : "Agg",                                       # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
                    "figure.dpi" : 300,
                "figure.figsize" : (9.6, 7.2),                                  # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes
                     "font.size" : 8,
            }
        )
        import matplotlib.pyplot
    except:
        raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
    try:
        import numpy
    except:
        raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None

    # Import my modules ...
    try:
        import pyguymer3
        import pyguymer3.geo
        import pyguymer3.image
    except:
        raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None

    # **************************************************************************

    # Start session ...
    with pyguymer3.start_session() as sess:
        # Check if the database is missing ...
        if not os.path.exists("hillcsv.zip"):
            # Download the database ...
            pyguymer3.download_file(sess, "http://www.hills-database.co.uk/hillcsv.zip", "hillcsv.zip")

    # **************************************************************************

    # Initialize counter ...
    n = 0                                                                       # [#]

    # Initialize lists ...
    x = []                                                                      # [°]
    y = []                                                                      # [°]
    z = []                                                                      # [m]
    xRaw = []                                                                   # [°]
    yRaw = []                                                                   # [°]

    # Open output file ...
    with open("british-hills-E600.txt", "wt", encoding = "utf-8") as fObjOut:
        # Open input file ...
        with zipfile.ZipFile("hillcsv.zip", "r") as fObjIn:
            # Loop over members ...
            for fname in fObjIn.namelist():
                # Skip those that are not CSVs ...
                if not fname.endswith(".csv"):
                    continue

                # Read CSV dataset as a list of strings ...
                rows = fObjIn.read(fname).decode("ascii").splitlines()

                # Loop over rows ...
                for row in csv.DictReader(rows):
                    # Append to lists ...
                    xRaw.append(float(row["Longitude"]))                        # [°]
                    yRaw.append(float(row["Latitude"]))                         # [°]

                    # Skip this row if the elevation is too small ...
                    if float(row["Metres"]) < 600.0:
                        continue

                    # Increment counter ...
                    n += 1                                                      # [#]

                    # Append to lists ...
                    x.append(float(row["Longitude"]))                           # [°]
                    y.append(float(row["Latitude"]))                            # [°]
                    z.append(float(row["Metres"]))                              # [m]

                    # Write summary ...
                    fObjOut.write(f'"{row["Name"]}" is {float(row["Metres"]):,.1f}m ASL with a prominence of {float(row["Drop"]):,.1f}m at ({float(row["Latitude"]):f}°,{float(row["Longitude"]):f}°).\n')

    # Print summary ...
    print(f"There are {n:,d} E600 mountains in The British Isles.")

    # **************************************************************************

    # Load tile metadata ...
    with open("OrdnanceSurveyBackgroundImages/miniscale.json", "rt", encoding = "utf-8") as fObj:
        meta = json.load(fObj)

    # Convert lists to arrays and find the sorted keys ...
    x = numpy.array(x)                                                          # [°]
    y = numpy.array(y)                                                          # [°]
    z = numpy.array(z)                                                          # [m]
    k = numpy.argsort(z)
    xRaw = numpy.array(xRaw)                                                    # [°]
    yRaw = numpy.array(yRaw)                                                    # [°]

    # Find middle of points and the furthest distance ...
    midLon, midLat, maxDist = pyguymer3.geo.find_middle_of_locs(
        xRaw,
        yRaw,
        pad = 12.0 * 1852.0,
    )                                                                           # [°], [°], [m]

    # Create figure ...
    fg = matplotlib.pyplot.figure()

    # Create axis ...
    ax = pyguymer3.geo.add_axis(
        fg,
        dist = maxDist,
         lat = midLat,
         lon = midLon,
    )

    # Add background image ...
    ax.imshow(
        matplotlib.pyplot.imread(f'OrdnanceSurveyBackgroundImages/{meta["MiniScale_(mono)_R22"]["greyscale"]}'),
                 cmap = "gray",
               extent = meta["MiniScale_(mono)_R22"]["extent"],
        interpolation = "bicubic",
               origin = "upper",
            transform = cartopy.crs.OSGB(),
                 vmax = 1.0,
                 vmin = 0.0,
    )

    # Plot data (layering them correctly) ...
    # NOTE: As of 5/Dec/2023, the default "zorder" of the coastlines is 1.5, the
    #       default "zorder" of the gridlines is 2.0 and the default "zorder" of
    #       the scattered points is 1.0.
    sc = ax.scatter(
        x[k],
        y[k],
                 c = z[k],
              cmap = matplotlib.pyplot.cm.rainbow,
        edgecolors = "black",
         linewidth = 0.1,
                 s = 10.0,
         transform = cartopy.crs.PlateCarree(),
              vmin = 600.0,
            zorder = 5.0,
    )

    # Add colour bar ...
    cb = fg.colorbar(sc, ax = ax, orientation = "vertical")

    # Configure axis ...
    ax.set_title("E600 Mountains In The British Isles")

    # Configure colour bar ...
    cb.set_label("Elevation [m]")

    # Configure figure ...
    fg.tight_layout()

    # Save figure ...
    fg.savefig("british-hills-E600.png")
    matplotlib.pyplot.close(fg)

    # Optimize PNG ...
    pyguymer3.image.optimize_image("british-hills-E600.png", strip = True)

              
You may also download “british-hills-E600.py” directly or view “british-hills-E600.py” on GitHub Gist (you may need to manually checkout the “main” branch).
  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
178
179
180
181
182
183
184
185
186
187
188
189

#!/usr/bin/env python3

# Use the proper idiom in the main module ...
# NOTE: See https://docs.python.org/3.12/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
if __name__ == "__main__":
    # NOTE: "The Database of British and Irish Hills" can be visited here:
    #         * http://www.hills-database.co.uk/
    #       ... and the links to download the database can be found here:
    #         * http://www.hills-database.co.uk/downloads.html

    # Import standard modules ...
    import csv
    import json
    import os
    import zipfile

    # Import special modules ...
    try:
        import cartopy
        cartopy.config.update(
            {
                "cache_dir" : os.path.expanduser("~/.local/share/cartopy_cache"),
            }
        )
    except:
        raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
    try:
        import matplotlib
        matplotlib.rcParams.update(
            {
                       "backend" : "Agg",                                       # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
                    "figure.dpi" : 300,
                "figure.figsize" : (9.6, 7.2),                                  # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes
                     "font.size" : 8,
            }
        )
        import matplotlib.pyplot
    except:
        raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
    try:
        import numpy
    except:
        raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None

    # Import my modules ...
    try:
        import pyguymer3
        import pyguymer3.geo
        import pyguymer3.image
    except:
        raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None

    # **************************************************************************

    # Start session ...
    with pyguymer3.start_session() as sess:
        # Check if the database is missing ...
        if not os.path.exists("hillcsv.zip"):
            # Download the database ...
            pyguymer3.download_file(sess, "http://www.hills-database.co.uk/hillcsv.zip", "hillcsv.zip")

    # **************************************************************************

    # Initialize counter ...
    n = 0                                                                       # [#]

    # Initialize lists ...
    x = []                                                                      # [°]
    y = []                                                                      # [°]
    z = []                                                                      # [m]
    xRaw = []                                                                   # [°]
    yRaw = []                                                                   # [°]

    # Open output file ...
    with open("british-hills-E900.txt", "wt", encoding = "utf-8") as fObjOut:
        # Open input file ...
        with zipfile.ZipFile("hillcsv.zip", "r") as fObjIn:
            # Loop over members ...
            for fname in fObjIn.namelist():
                # Skip those that are not CSVs ...
                if not fname.endswith(".csv"):
                    continue

                # Read CSV dataset as a list of strings ...
                rows = fObjIn.read(fname).decode("ascii").splitlines()

                # Loop over rows ...
                for row in csv.DictReader(rows):
                    # Append to lists ...
                    xRaw.append(float(row["Longitude"]))                        # [°]
                    yRaw.append(float(row["Latitude"]))                         # [°]

                    # Skip this row if the elevation is too small ...
                    if float(row["Metres"]) < 900.0:
                        continue

                    # Increment counter ...
                    n += 1                                                      # [#]

                    # Append to lists ...
                    x.append(float(row["Longitude"]))                           # [°]
                    y.append(float(row["Latitude"]))                            # [°]
                    z.append(float(row["Metres"]))                              # [m]

                    # Write summary ...
                    fObjOut.write(f'"{row["Name"]}" is {float(row["Metres"]):,.1f}m ASL with a prominence of {float(row["Drop"]):,.1f}m at ({float(row["Latitude"]):f}°,{float(row["Longitude"]):f}°).\n')

    # Print summary ...
    print(f"There are {n:,d} E900 mountains in The British Isles.")

    # **************************************************************************

    # Load tile metadata ...
    with open("OrdnanceSurveyBackgroundImages/miniscale.json", "rt", encoding = "utf-8") as fObj:
        meta = json.load(fObj)

    # Convert lists to arrays and find the sorted keys ...
    x = numpy.array(x)                                                          # [°]
    y = numpy.array(y)                                                          # [°]
    z = numpy.array(z)                                                          # [m]
    k = numpy.argsort(z)
    xRaw = numpy.array(xRaw)                                                    # [°]
    yRaw = numpy.array(yRaw)                                                    # [°]

    # Find middle of points and the furthest distance ...
    midLon, midLat, maxDist = pyguymer3.geo.find_middle_of_locs(
        xRaw,
        yRaw,
        pad = 12.0 * 1852.0,
    )                                                                           # [°], [°], [m]

    # Create figure ...
    fg = matplotlib.pyplot.figure()

    # Create axis ...
    ax = pyguymer3.geo.add_axis(
        fg,
        dist = maxDist,
         lat = midLat,
         lon = midLon,
    )

    # Add background image ...
    ax.imshow(
        matplotlib.pyplot.imread(f'OrdnanceSurveyBackgroundImages/{meta["MiniScale_(mono)_R22"]["greyscale"]}'),
                 cmap = "gray",
               extent = meta["MiniScale_(mono)_R22"]["extent"],
        interpolation = "bicubic",
               origin = "upper",
            transform = cartopy.crs.OSGB(),
                 vmax = 1.0,
                 vmin = 0.0,
    )

    # Plot data (layering them correctly) ...
    # NOTE: As of 5/Dec/2023, the default "zorder" of the coastlines is 1.5, the
    #       default "zorder" of the gridlines is 2.0 and the default "zorder" of
    #       the scattered points is 1.0.
    sc = ax.scatter(
        x[k],
        y[k],
                 c = z[k],
              cmap = matplotlib.pyplot.cm.rainbow,
        edgecolors = "black",
         linewidth = 0.1,
                 s = 10.0,
         transform = cartopy.crs.PlateCarree(),
              vmin = 900.0,
            zorder = 5.0,
    )

    # Add colour bar ...
    cb = fg.colorbar(sc, ax = ax, orientation = "vertical")

    # Configure axis ...
    ax.set_title("E900 Mountains In The British Isles")

    # Configure colour bar ...
    cb.set_label("Elevation [m]")

    # Configure figure ...
    fg.tight_layout()

    # Save figure ...
    fg.savefig("british-hills-E900.png")
    matplotlib.pyplot.close(fg)

    # Optimize PNG ...
    pyguymer3.image.optimize_image("british-hills-E900.png", strip = True)

              
You may also download “british-hills-E900.py” directly or view “british-hills-E900.py” on GitHub Gist (you may need to manually checkout the “main” branch).
  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
178
179
180
181
182
183
184
185
186
187
188
189
190

#!/usr/bin/env python3

# Use the proper idiom in the main module ...
# NOTE: See https://docs.python.org/3.12/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
if __name__ == "__main__":
    # NOTE: "The Database of British and Irish Hills" can be visited here:
    #         * http://www.hills-database.co.uk/
    #       ... and the links to download the database can be found here:
    #         * http://www.hills-database.co.uk/downloads.html
    # NOTE: https://en.wikipedia.org/wiki/List_of_P600_mountains_in_the_British_Isles

    # Import standard modules ...
    import csv
    import json
    import os
    import zipfile

    # Import special modules ...
    try:
        import cartopy
        cartopy.config.update(
            {
                "cache_dir" : os.path.expanduser("~/.local/share/cartopy_cache"),
            }
        )
    except:
        raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
    try:
        import matplotlib
        matplotlib.rcParams.update(
            {
                       "backend" : "Agg",                                       # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
                    "figure.dpi" : 300,
                "figure.figsize" : (9.6, 7.2),                                  # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes
                     "font.size" : 8,
            }
        )
        import matplotlib.pyplot
    except:
        raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
    try:
        import numpy
    except:
        raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None

    # Import my modules ...
    try:
        import pyguymer3
        import pyguymer3.geo
        import pyguymer3.image
    except:
        raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None

    # **************************************************************************

    # Start session ...
    with pyguymer3.start_session() as sess:
        # Check if the database is missing ...
        if not os.path.exists("hillcsv.zip"):
            # Download the database ...
            pyguymer3.download_file(sess, "http://www.hills-database.co.uk/hillcsv.zip", "hillcsv.zip")

    # **************************************************************************

    # Initialize counter ...
    n = 0                                                                       # [#]

    # Initialize lists ...
    x = []                                                                      # [°]
    y = []                                                                      # [°]
    z = []                                                                      # [m]
    xRaw = []                                                                   # [°]
    yRaw = []                                                                   # [°]

    # Open output file ...
    with open("british-hills-P600.txt", "wt", encoding = "utf-8") as fObjOut:
        # Open input file ...
        with zipfile.ZipFile("hillcsv.zip", "r") as fObjIn:
            # Loop over members ...
            for fname in fObjIn.namelist():
                # Skip those that are not CSVs ...
                if not fname.endswith(".csv"):
                    continue

                # Read CSV dataset as a list of strings ...
                rows = fObjIn.read(fname).decode("ascii").splitlines()

                # Loop over rows ...
                for row in csv.DictReader(rows):
                    # Append to lists ...
                    xRaw.append(float(row["Longitude"]))                        # [°]
                    yRaw.append(float(row["Latitude"]))                         # [°]

                    # Skip this row if the prominence is too small ...
                    if float(row["Drop"]) < 600.0:
                        continue

                    # Increment counter ...
                    n += 1                                                      # [#]

                    # Append to lists ...
                    x.append(float(row["Longitude"]))                           # [°]
                    y.append(float(row["Latitude"]))                            # [°]
                    z.append(float(row["Drop"]))                                # [m]

                    # Write summary ...
                    fObjOut.write(f'"{row["Name"]}" is {float(row["Metres"]):,.1f}m ASL with a prominence of {float(row["Drop"]):,.1f}m at ({float(row["Latitude"]):f}°,{float(row["Longitude"]):f}°).\n')

    # Print summary ...
    print(f"There are {n:,d} P600 mountains in The British Isles.")

    # **************************************************************************

    # Load tile metadata ...
    with open("OrdnanceSurveyBackgroundImages/miniscale.json", "rt", encoding = "utf-8") as fObj:
        meta = json.load(fObj)

    # Convert lists to arrays and find the sorted keys ...
    x = numpy.array(x)                                                          # [°]
    y = numpy.array(y)                                                          # [°]
    z = numpy.array(z)                                                          # [m]
    k = numpy.argsort(z)
    xRaw = numpy.array(xRaw)                                                    # [°]
    yRaw = numpy.array(yRaw)                                                    # [°]

    # Find middle of points and the furthest distance ...
    midLon, midLat, maxDist = pyguymer3.geo.find_middle_of_locs(
        xRaw,
        yRaw,
        pad = 12.0 * 1852.0,
    )                                                                           # [°], [°], [m]

    # Create figure ...
    fg = matplotlib.pyplot.figure()

    # Create axis ...
    ax = pyguymer3.geo.add_axis(
        fg,
        dist = maxDist,
         lat = midLat,
         lon = midLon,
    )

    # Add background image ...
    ax.imshow(
        matplotlib.pyplot.imread(f'OrdnanceSurveyBackgroundImages/{meta["MiniScale_(mono)_R22"]["greyscale"]}'),
                 cmap = "gray",
               extent = meta["MiniScale_(mono)_R22"]["extent"],
        interpolation = "bicubic",
               origin = "upper",
            transform = cartopy.crs.OSGB(),
                 vmax = 1.0,
                 vmin = 0.0,
    )

    # Plot data (layering them correctly) ...
    # NOTE: As of 5/Dec/2023, the default "zorder" of the coastlines is 1.5, the
    #       default "zorder" of the gridlines is 2.0 and the default "zorder" of
    #       the scattered points is 1.0.
    sc = ax.scatter(
        x[k],
        y[k],
                 c = z[k],
              cmap = matplotlib.pyplot.cm.rainbow,
        edgecolors = "black",
         linewidth = 0.1,
                 s = 10.0,
         transform = cartopy.crs.PlateCarree(),
              vmin = 600.0,
            zorder = 5.0,
    )

    # Add colour bar ...
    cb = fg.colorbar(sc, ax = ax, orientation = "vertical")

    # Configure axis ...
    ax.set_title("P600 Mountains In The British Isles")

    # Configure colour bar ...
    cb.set_label("Prominence [m]")

    # Configure figure ...
    fg.tight_layout()

    # Save figure ...
    fg.savefig("british-hills-P600.png")
    matplotlib.pyplot.close(fg)

    # Optimize PNG ...
    pyguymer3.image.optimize_image("british-hills-P600.png", strip = True)

              
You may also download “british-hills-P600.py” directly or view “british-hills-P600.py” on GitHub Gist (you may need to manually checkout the “main” branch).
  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
178
179
180
181
182
183
184
185
186
187
188
189

#!/usr/bin/env python3

# Use the proper idiom in the main module ...
# NOTE: See https://docs.python.org/3.12/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
if __name__ == "__main__":
    # NOTE: "The Database of British and Irish Hills" can be visited here:
    #         * http://www.hills-database.co.uk/
    #       ... and the links to download the database can be found here:
    #         * http://www.hills-database.co.uk/downloads.html

    # Import standard modules ...
    import csv
    import json
    import os
    import zipfile

    # Import special modules ...
    try:
        import cartopy
        cartopy.config.update(
            {
                "cache_dir" : os.path.expanduser("~/.local/share/cartopy_cache"),
            }
        )
    except:
        raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
    try:
        import matplotlib
        matplotlib.rcParams.update(
            {
                       "backend" : "Agg",                                       # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
                    "figure.dpi" : 300,
                "figure.figsize" : (9.6, 7.2),                                  # NOTE: See https://github.com/Guymer/misc/blob/main/README.md#matplotlib-figure-sizes
                     "font.size" : 8,
            }
        )
        import matplotlib.pyplot
    except:
        raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
    try:
        import numpy
    except:
        raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None

    # Import my modules ...
    try:
        import pyguymer3
        import pyguymer3.geo
        import pyguymer3.image
    except:
        raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None

    # **************************************************************************

    # Start session ...
    with pyguymer3.start_session() as sess:
        # Check if the database is missing ...
        if not os.path.exists("hillcsv.zip"):
            # Download the database ...
            pyguymer3.download_file(sess, "http://www.hills-database.co.uk/hillcsv.zip", "hillcsv.zip")

    # **************************************************************************

    # Initialize counter ...
    n = 0                                                                       # [#]

    # Initialize lists ...
    x = []                                                                      # [°]
    y = []                                                                      # [°]
    z = []                                                                      # [m]
    xRaw = []                                                                   # [°]
    yRaw = []                                                                   # [°]

    # Open output file ...
    with open("british-hills-P900.txt", "wt", encoding = "utf-8") as fObjOut:
        # Open input file ...
        with zipfile.ZipFile("hillcsv.zip", "r") as fObjIn:
            # Loop over members ...
            for fname in fObjIn.namelist():
                # Skip those that are not CSVs ...
                if not fname.endswith(".csv"):
                    continue

                # Read CSV dataset as a list of strings ...
                rows = fObjIn.read(fname).decode("ascii").splitlines()

                # Loop over rows ...
                for row in csv.DictReader(rows):
                    # Append to lists ...
                    xRaw.append(float(row["Longitude"]))                        # [°]
                    yRaw.append(float(row["Latitude"]))                         # [°]

                    # Skip this row if the prominence is too small ...
                    if float(row["Drop"]) < 900.0:
                        continue

                    # Increment counter ...
                    n += 1                                                      # [#]

                    # Append to lists ...
                    x.append(float(row["Longitude"]))                           # [°]
                    y.append(float(row["Latitude"]))                            # [°]
                    z.append(float(row["Drop"]))                                # [m]

                    # Write summary ...
                    fObjOut.write(f'"{row["Name"]}" is {float(row["Metres"]):,.1f}m ASL with a prominence of {float(row["Drop"]):,.1f}m at ({float(row["Latitude"]):f}°,{float(row["Longitude"]):f}°).\n')

    # Print summary ...
    print(f"There are {n:,d} P900 mountains in The British Isles.")

    # **************************************************************************

    # Load tile metadata ...
    with open("OrdnanceSurveyBackgroundImages/miniscale.json", "rt", encoding = "utf-8") as fObj:
        meta = json.load(fObj)

    # Convert lists to arrays and find the sorted keys ...
    x = numpy.array(x)                                                          # [°]
    y = numpy.array(y)                                                          # [°]
    z = numpy.array(z)                                                          # [m]
    k = numpy.argsort(z)
    xRaw = numpy.array(xRaw)                                                    # [°]
    yRaw = numpy.array(yRaw)                                                    # [°]

    # Find middle of points and the furthest distance ...
    midLon, midLat, maxDist = pyguymer3.geo.find_middle_of_locs(
        xRaw,
        yRaw,
        pad = 12.0 * 1852.0,
    )                                                                           # [°], [°], [m]

    # Create figure ...
    fg = matplotlib.pyplot.figure()

    # Create axis ...
    ax = pyguymer3.geo.add_axis(
        fg,
        dist = maxDist,
         lat = midLat,
         lon = midLon,
    )

    # Add background image ...
    ax.imshow(
        matplotlib.pyplot.imread(f'OrdnanceSurveyBackgroundImages/{meta["MiniScale_(mono)_R22"]["greyscale"]}'),
                 cmap = "gray",
               extent = meta["MiniScale_(mono)_R22"]["extent"],
        interpolation = "bicubic",
               origin = "upper",
            transform = cartopy.crs.OSGB(),
                 vmax = 1.0,
                 vmin = 0.0,
    )

    # Plot data (layering them correctly) ...
    # NOTE: As of 5/Dec/2023, the default "zorder" of the coastlines is 1.5, the
    #       default "zorder" of the gridlines is 2.0 and the default "zorder" of
    #       the scattered points is 1.0.
    sc = ax.scatter(
        x[k],
        y[k],
                 c = z[k],
              cmap = matplotlib.pyplot.cm.rainbow,
        edgecolors = "black",
         linewidth = 0.1,
                 s = 10.0,
         transform = cartopy.crs.PlateCarree(),
              vmin = 900.0,
            zorder = 5.0,
    )

    # Add colour bar ...
    cb = fg.colorbar(sc, ax = ax, orientation = "vertical")

    # Configure axis ...
    ax.set_title("P900 Mountains In The British Isles")

    # Configure colour bar ...
    cb.set_label("Prominence [m]")

    # Configure figure ...
    fg.tight_layout()

    # Save figure ...
    fg.savefig("british-hills-P900.png")
    matplotlib.pyplot.close(fg)

    # Optimize PNG ...
    pyguymer3.image.optimize_image("british-hills-P900.png", strip = True)

              
You may also download “british-hills-P900.py” directly or view “british-hills-P900.py” on GitHub Gist (you may need to manually checkout the “main” branch).