• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ssec / sift / 13528712524

25 Feb 2025 06:50PM UTC coverage: 29.691% (-20.2%) from 49.871%
13528712524

push

github

web-flow
Merge pull request #437 from ameraner/fix_export_image_float

Deactivate export image and rgb config tests to avoid Segfaults in tests and add explicit float casting for Fraction call to fix export tests

0 of 1 new or added line in 1 file covered. (0.0%)

2747 existing lines in 33 files now uncovered.

4386 of 14772 relevant lines covered (29.69%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.05
/uwsift/view/transform.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""VisPy Transform objects to handle some of the more complex projections.
2✔
4

5
SIFT uses PROJ.4 to define geographic projections and these are rarely
6
possible to implement in Matrix transforms that come with VisPy.
7

8
"""
9

10
import re
2✔
11
from os import linesep as os_linesep
2✔
12
from typing import List
2✔
13

14
import numpy as np
2✔
15
from pyproj import Proj, pj_ellps
2✔
16
from vispy import glsl
2✔
17
from vispy.visuals.shaders import Function
2✔
18
from vispy.visuals.shaders.expression import TextExpression
2✔
19
from vispy.visuals.shaders.parsing import find_program_variables
2✔
20
from vispy.visuals.transforms import BaseTransform
2✔
21
from vispy.visuals.transforms._util import arg_to_vec4
2✔
22

23

24
class VariableDeclaration(TextExpression):
2✔
25
    """TextExpression subclass for exposing GLSL variables to vispy glsl interface.
26

27
    Parameters
28
    ----------
29
    name : str
30
        Name of the variable.
31
    text : str
32
        Rvalue to be assigned to the variable.
33
    """
34

35
    def __init__(self, name: str, text: str) -> None:
2✔
36
        self._name = name
2✔
37
        super().__init__(text)
2✔
38

39
    def definition(self, names, version=None, shader=None) -> str:
2✔
UNCOV
40
        return self.text
×
41

42
    @property
2✔
43
    def name(self) -> str:
2✔
UNCOV
44
        return self._name
×
45

46

47
class GLSL_Adapter(TextExpression):
2✔
48
    """TextExpression subclass for parsing Macro definitions from .glsl header files and exposing them to vispy.
49

50
    This class makes macro definitions accessible to vispy's shader code
51
    processing. Assumes .glsl code to be parsed is accessible as python string. For reading
52
    .glsl header code from a file see GLSL_FileAdapter subclass.
53

54
    Parameters
55
    ----------
56
    text : str
57
        Actual .glsl code string.
58
    """
59

60
    _expr_list: list = []
2✔
61

62
    def __init__(self, text: str) -> None:
2✔
63
        # Regular expression for parsing possibly include-guarded macro definitions from .glsl
64
        # header files; makes strong assumptions about formatting of macro names by assuming
65
        # underscores in front of and behind the macro name. In line with vispy .glsl shader code.
66
        guard_pattern = re.compile(r"^#ifndef\s*(?P<guard>_[A-Za-z]*_)")
2✔
67
        _guard_flag = False
2✔
68
        for line in text.splitlines():
2✔
69
            match_guard = guard_pattern.match(line)
2✔
70
            var_match = find_program_variables(line)
2✔
71
            if match_guard is not None:
2✔
72
                _name = match_guard["guard"]
2✔
73
                _text = match_guard.group(0) + os_linesep + f"#define {_name}"
2✔
74
                self._expr_list.append(VariableDeclaration(_name, _text))
2✔
75
                self._expr_list.append(VariableDeclaration(_name + "EIF", "#endif"))
2✔
76
                _guard_flag = True
2✔
77
            elif var_match is not None:
2✔
78
                key_list = list(var_match.keys())
2✔
79
                if len(key_list) > 1:
2✔
80
                    raise ValueError("More than one variable definition per line " "not supported.")
×
81
                elif len(key_list) != 0:
2✔
82
                    self._expr_list.append(VariableDeclaration(key_list[0], line))
2✔
83
        if _guard_flag:
2✔
84
            # in case of include guards, shift #endif to bottom of
85
            # expression list to match #ifndef
86
            eif_token = self._expr_list[1]
2✔
87
            self._expr_list[1:-1] = self._expr_list[2:]
2✔
88
            self._expr_list[-1] = eif_token
2✔
89

90
    @property
2✔
91
    def expr_list(self) -> List[VariableDeclaration]:
2✔
92
        return self._expr_list
2✔
93

94

95
class GLSL_FileAdapter(GLSL_Adapter):
2✔
96
    """GLSL_Adapter subclass adding the functionality to read .glsl header code from files.
97

98
    Parameters
99
    ----------
100
    file_path : str
101
        Path to .glsl header file.
102
    """
103

104
    def __init__(self, file_path: str) -> None:
2✔
105
        text = glsl.get(file_path)
2✔
106
        super(GLSL_FileAdapter, self).__init__(text)
2✔
107

108

109
COMMON_VALUES_DEF = """const float SPI = 3.14159265359;
2✔
110
const float TWOPI = 6.2831853071795864769;
111
const float ONEPI = 3.14159265358979323846;
112
const float M_FORTPI = M_PI_4;                      /* pi/4 */
113
const float M_HALFPI = M_PI_2;                      /* pi/2 */
114
const float M_PI_HALFPI = 4.71238898038468985769;   /* 1.5*pi */
115
const float M_TWOPI = 6.28318530717958647693;       /* 2*pi */
116
const float M_TWO_D_PI = 2.0/M_PI;                  /* 2/pi */
117
const float M_TWOPI_HALFPI = 2.5 / M_PI;            /* 2.5*pi */
118
"""
119

120
math_consts = GLSL_FileAdapter("math/constants.glsl").expr_list
2✔
121
COMMON_VALUES = GLSL_Adapter(COMMON_VALUES_DEF).expr_list
2✔
122
M_FORTPI = M_PI_4 = 0.78539816339744828
2✔
123
M_HALFPI = M_PI_2 = 1.57079632679489660
2✔
124

125

126
def merc_init(proj_dict):
2✔
127
    proj_dict.setdefault("lon_0", 0.0)
×
128
    proj_dict.setdefault("k0", 1.0)
×
129

130
    phits = 0.0
×
131
    is_phits = "lat_ts" in proj_dict
×
132
    if is_phits:
×
133
        phits = np.radians(proj_dict["lat_ts"])
×
134
        if phits >= M_HALFPI:
×
135
            raise ValueError("PROJ.4 'lat_ts' parameter must be greater than PI/2")
×
136

137
    if proj_dict["a"] != proj_dict["b"]:
×
138
        # ellipsoid
139
        if is_phits:
×
140
            proj_dict["k0"] = pj_msfn_py(np.sin(phits), np.cos(phits), proj_dict["es"])
×
141
    elif is_phits:
×
142
        # spheroid
143
        proj_dict["k0"] = np.cos(phits)
×
144

145
    return proj_dict
×
146

147

148
def lcc_init(proj_dict):
2✔
149
    if "lat_1" not in proj_dict:
×
150
        raise ValueError("PROJ.4 'lat_1' parameter is required for 'lcc' projection")
×
151

152
    proj_dict.setdefault("lon_0", 0.0)
×
153
    if "lat_2" not in proj_dict:
×
154
        proj_dict["lat_2"] = proj_dict["lat_1"]
×
155
        if "lat_0" not in proj_dict:
×
156
            proj_dict["lat_0"] = proj_dict["lat_1"]
×
157
    proj_dict["phi1"] = np.radians(proj_dict["lat_1"])
×
158
    proj_dict["phi2"] = np.radians(proj_dict["lat_2"])
×
159
    proj_dict["phi0"] = np.radians(proj_dict["lat_0"])
×
160

161
    if abs(proj_dict["phi1"] + proj_dict["phi2"]) < 1e-10:
×
162
        raise ValueError("'lat_1' + 'lat_2' for 'lcc' projection when converted to radians must be greater than 1e-10.")
×
163

164
    proj_dict["n"] = sinphi = np.sin(proj_dict["phi1"])
×
165
    cosphi = np.cos(proj_dict["phi1"])
×
166
    secant = abs(proj_dict["phi1"] - proj_dict["phi2"]) >= 1e-10
×
167
    proj_dict["ellips"] = proj_dict["a"] != proj_dict["b"]
×
168
    if proj_dict["ellips"]:
×
169
        # ellipsoid
170
        m1 = pj_msfn_py(sinphi, cosphi, proj_dict["es"])
×
171
        ml1 = pj_tsfn_py(proj_dict["phi1"], sinphi, proj_dict["e"])
×
172
        if secant:
×
173
            sinphi = np.sin(proj_dict["phi2"])
×
174
            proj_dict["n"] = np.log(m1 / pj_msfn_py(sinphi, np.cos(proj_dict["phi2"]), proj_dict["es"]))
×
175
            proj_dict["n"] /= np.log(ml1 / pj_tsfn_py(proj_dict["phi2"], sinphi, proj_dict["e"]))
×
176
        proj_dict["c"] = proj_dict["rho0"] = m1 * pow(ml1, -proj_dict["n"]) / proj_dict["n"]
×
177
        proj_dict["rho0"] *= (
×
178
            0.0
179
            if abs(abs(proj_dict["phi0"]) - M_HALFPI) < 1e-10
180
            else pow(pj_tsfn_py(proj_dict["phi0"], np.sin(proj_dict["phi0"]), proj_dict["e"]), proj_dict["n"])
181
        )
182
    else:
183
        # spheroid
184
        if secant:
×
185
            proj_dict["n"] = np.log(cosphi / np.cos(proj_dict["phi2"])) / np.log(
×
186
                np.tan(M_FORTPI + 0.5 * proj_dict["phi2"]) / np.tan(M_FORTPI + 0.5 * proj_dict["phi1"])
187
            )
188
        proj_dict["c"] = cosphi * pow(np.tan(M_FORTPI + 0.5 * proj_dict["phi1"]), proj_dict["n"]) / proj_dict["n"]
×
189
        proj_dict["rho0"] = (
×
190
            0.0
191
            if abs(abs(proj_dict["phi0"]) - M_HALFPI) < 1e-10
192
            else proj_dict["c"] * pow(np.tan(M_FORTPI + 0.5 * proj_dict["phi0"]), -proj_dict["n"])
193
        )
194
    proj_dict["ellips"] = "true" if proj_dict["ellips"] else "false"
×
195
    return proj_dict
×
196

197

198
def geos_init(proj_dict):
2✔
UNCOV
199
    if "h" not in proj_dict:
×
200
        raise ValueError("PROJ.4 'h' parameter is required for 'geos' projection")
×
201

UNCOV
202
    proj_dict.setdefault("lat_0", 0.0)
×
UNCOV
203
    proj_dict.setdefault("lon_0", 0.0)
×
204
    # lat_0 is set to phi0 in the PROJ.4 C source code
205
    # if 'lat_0' not in proj_dict:
206
    #     raise ValueError("PROJ.4 'lat_0' parameter is required for 'geos' projection")
207

UNCOV
208
    if "sweep" not in proj_dict or proj_dict["sweep"] is None:
×
UNCOV
209
        proj_dict["flip_axis"] = "false"
×
210
    elif proj_dict["sweep"] not in ["x", "y"]:
×
211
        raise ValueError("PROJ.4 'sweep' parameter must be 'x' or 'y'")
×
212
    elif proj_dict["sweep"] == "x":
×
213
        proj_dict["flip_axis"] = "true"
×
214
    else:
215
        proj_dict["flip_axis"] = "false"
×
216

UNCOV
217
    proj_dict["radius_g_1"] = proj_dict["h"] / proj_dict["a"]
×
UNCOV
218
    proj_dict["radius_g"] = 1.0 + proj_dict["radius_g_1"]
×
UNCOV
219
    proj_dict["C"] = proj_dict["radius_g"] * proj_dict["radius_g"] - 1.0
×
UNCOV
220
    if proj_dict["a"] != proj_dict["b"]:
×
221
        # ellipsoid
UNCOV
222
        proj_dict["one_es"] = 1.0 - proj_dict["es"]
×
UNCOV
223
        proj_dict["rone_es"] = 1.0 / proj_dict["one_es"]
×
UNCOV
224
        proj_dict["radius_p"] = np.sqrt(proj_dict["one_es"])
×
UNCOV
225
        proj_dict["radius_p2"] = proj_dict["one_es"]
×
UNCOV
226
        proj_dict["radius_p_inv2"] = proj_dict["rone_es"]
×
227
    else:
228
        proj_dict["radius_p"] = proj_dict["radius_p2"] = proj_dict["radius_p_inv2"] = 1.0
×
UNCOV
229
    return proj_dict
×
230

231

232
def stere_init(proj_dict):
2✔
233
    # Calculate phits
234
    phits = abs(np.radians(proj_dict["lat_ts"]) if "lat_ts" in proj_dict else M_HALFPI)
×
235
    # Determine mode
236
    if abs(abs(np.radians(proj_dict["lat_0"])) - M_HALFPI) < 1e-10:
×
237
        # Assign "mode" in proj_dict to be GLSL for specific case (make sure to handle C-code case fallthrough):
238
        # 0 = n_pole, 1 = s_pole.
239
        proj_dict["mode"] = 1 if proj_dict["lat_0"] < 0 else 0
×
240
        if proj_dict["a"] != proj_dict["b"]:
×
241
            # ellipsoid
242
            e = proj_dict["e"]
×
243
            if abs(phits - M_HALFPI) < 1e-10:
×
244
                proj_dict["akm1"] = 2.0 / np.sqrt((1 + e) ** (1 + e) * (1 - e) ** (1 - e))
×
245
            else:
246
                proj_dict["akm1"] = np.cos(phits) / (
×
247
                    pj_tsfn_py(phits, np.sin(phits), e) * np.sqrt(1.0 - (np.sin(phits) * e) ** 2)
248
                )
249
        else:
250
            # sphere
251
            proj_dict["akm1"] = (
×
252
                np.cos(phits) / np.tan(M_FORTPI - 0.5 * phits) if abs(phits - M_HALFPI) >= 1e-10 else 2.0
253
            )
254
    else:
255
        # If EQUIT or OBLIQ mode:
256
        raise NotImplementedError("This projection mode is not supported yet.")
×
257
    return proj_dict
×
258

259

260
def eqc_init(proj_dict):
2✔
261
    proj_dict.setdefault("lat_0", 0.0)
×
262
    proj_dict.setdefault("lat_ts", proj_dict["lat_0"])
×
263
    proj_dict["rc"] = np.cos(np.radians(proj_dict["lat_ts"]))
×
264
    if proj_dict["rc"] <= 0.0:
×
265
        raise ValueError("PROJ.4 'lat_ts' parameter must be in range (-PI/2,PI/2)")
×
266
    proj_dict["phi0"] = np.radians(proj_dict["lat_0"])
×
267
    proj_dict["es"] = 0.0
×
268
    return proj_dict
×
269

270

271
def latlong_init(proj_dict):
2✔
272
    if "over" in proj_dict:
×
273
        # proj_dict['offset'] = '360.'
274
        proj_dict["offset"] = "0."
×
275
    else:
276
        proj_dict["offset"] = "0."
×
277
    return proj_dict
×
278

279

280
# proj_name -> (proj_init, map_ellps, map_spher, imap_ellps, imap_spher)
281
# where 'map' is lon/lat to X/Y
282
# and 'imap' is X/Y to lon/lat
283
# WARNING: Need double {{ }} for functions for string formatting to work properly
284
PROJECTIONS = {
2✔
285
    "longlat": (
286
        latlong_init,
287
        """vec4 latlong_map(vec4 pos) {{
288
            return vec4(pos.x + {offset}, pos.y, pos.z, pos.w);
289
        }}""",
290
        """vec4 latlong_map(vec4 pos) {{
291
            return vec4(pos.x + {offset}, pos.y, pos.z, pos.w);
292
        }}""",
293
        """vec4 latlong_imap(vec4 pos) {{
294
            return pos;
295
        }}""",
296
        """vec4 latlong_imap(vec4 pos) {{
297
            return pos;
298
        }}""",
299
    ),
300
    "merc": (
301
        merc_init,
302
        """vec4 merc_map_e(vec4 pos) {{
303
            float lambda = radians(pos.x);
304
            {over}
305
            float phi = radians(pos.y);
306
            if (abs(abs(phi) - M_HALFPI) <= 1.e-10) {{
307
                return vec4(1. / 0., 1. / 0., pos.z, pos.w);
308
            }}
309
            float x = {a} * {k0} * (lambda - {lon_0}f);
310
            float y = {a} * {k0} * -log(pj_tsfn(phi, sin(phi), {e}));
311
            return vec4(x, y, pos.z, pos.w);
312
        }}""",
313
        """vec4 merc_map_s(vec4 pos) {{
314
            float lambda = radians(pos.x);
315
            {over}
316
            float phi = radians(pos.y);
317
            if (abs(abs(phi) - M_HALFPI) <= 1.e-10) {{
318
                return vec4(1. / 0., 1. / 0., pos.z, pos.w);
319
            }}
320
            float x = {a} * {k0} * (lambda - {lon_0}f);
321
            float y = {a} * {k0} * log(tan(M_PI / 4.f + phi / 2.f));
322
            return vec4(x, y, pos.z, pos.w);
323
        }}""",
324
        """vec4 merc_imap_e(vec4 pos) {{
325
            float x = pos.x;
326
            float y = pos.y;
327
            float lambda = {lon_0}f + x / ({a} * {k0});
328
            {over}
329
            lambda = degrees(lambda);
330
            float phi = degrees(pj_phi2(exp(-y / ({a} * {k0})), {e}));
331
            return vec4(lambda, phi, pos.z, pos.w);
332
        }}""",
333
        """vec4 merc_imap_s(vec4 pos) {{
334
            float x = pos.x;
335
            float y = pos.y;
336
            float lambda = {lon_0}f + x / ({a} * {k0});
337
            {over}
338
            lambda = degrees(lambda);
339
            float phi = degrees(2.f * atan(exp(y / ({a} * {k0}))) - M_PI / 2.f);
340
            return vec4(lambda, phi, pos.z, pos.w);
341
        }}""",
342
    ),
343
    "lcc": (
344
        lcc_init,
345
        """vec4 lcc_map_e(vec4 pos) {{
346
            float rho;
347
            float lambda = radians(pos.x - {lon_0});
348
            float phi = radians(pos.y);
349
            {over}
350

351
            if (abs(abs(phi) - M_HALFPI) < 1e-10) {{
352
                if ((phi * {n}) <= 0.) {{
353
                    return vec4(1. / 0., 1. / 0., pos.z, pos.w);
354
                }}
355
                rho = 0.;
356
            }} else {{
357
                rho = {c} * ({ellips} ? pow(pj_tsfn(phi, sin(phi),
358
                    {e}), {n}) : pow(tan(M_FORTPI + .5 * phi), -1. * {n}));
359
            }}
360

361
            lambda *= {n};
362
            return vec4({a} * (rho * sin(lambda)), {a} * ({rho0} - rho * cos(lambda)), pos.z, pos.w);
363
        }}""",
364
        None,
365
        """vec4 lcc_imap_e(vec4 pos) {{
366
            float rho, phi, lambda;
367
            float x = pos.x / {a};
368
            float y = pos.y / {a};
369
            y = {rho0} - y;
370
            rho = hypot(x, y);
371
            if (rho != 0.0) {{
372
                if ({n} < 0.) {{
373
                    rho = -rho;
374
                    x = -x;
375
                    y = -y;
376
                }}
377
                if ({ellips}) {{
378
                    phi = pj_phi2(pow(rho / {c}, 1. / {n}), {e});
379
                    //if (phi == HUGE_VAL) {{
380
                    //    return vec4(1. / 0., 1. / 0., pos.z, pos.w);
381
                    //}}
382
                }} else {{
383
                    phi = 2. * atan(pow({c} / rho, 1. / {n})) - M_HALFPI;
384
                }}
385
                // atan2 in C
386
                lambda = atan(x, y) / {n};
387
            }} else {{
388
                lambda = 0.;
389
                phi = {n} > 0. ? M_HALFPI : - M_HALFPI;
390
            }}
391
            {over}
392
            return vec4(degrees(lambda) + {lon_0}, degrees(phi), pos.z, pos.w);
393
        }}""",
394
        None,
395
    ),
396
    "geos": (
397
        geos_init,
398
        """vec4 geos_map_e(vec4 pos) {{
399
            float lambda, phi, r, Vx, Vy, Vz, tmp, x, y;
400
            lambda = radians(pos.x - {lon_0});
401
            {over}
402
            phi = atan({radius_p2} * tan(radians(pos.y)));
403
            r = {radius_p} / hypot({radius_p} * cos(phi), sin(phi));
404
            Vx = r * cos(lambda) * cos(phi);
405
            Vy = r * sin(lambda) * cos(phi);
406
            Vz = r * sin(phi);
407

408
            // TODO: Best way to 'discard' a vertex
409
            if ((({radius_g} - Vx) * Vx - Vy * Vy - Vz * Vz * {radius_p_inv2}) < 0.) {{
410
               return vec4(1. / 0., 1. / 0., pos.z, pos.w);
411
            }}
412

413
            tmp = {radius_g} - Vx;
414

415
            if ({flip_axis}) {{
416
                x = {radius_g_1} * atan(Vy / hypot(Vz, tmp));
417
                y = {radius_g_1} * atan(Vz / tmp);
418
            }} else {{
419
                x = {radius_g_1} * atan(Vy / tmp);
420
                y = {radius_g_1} * atan(Vz / hypot(Vy, tmp));
421
            }}
422
            return vec4(x * {a}, y * {a}, pos.z, pos.w);
423
        }}""",
424
        """vec4 geos_map_s(vec4 pos) {{
425
            float lambda, phi, Vx, Vy, Vz, tmp, x, y;
426
            lambda = radians(pos.x - {lon_0});
427
            {over}
428
            phi = radians(pos.y);
429
            Vx = cos(lambda) * cos(phi);
430
            Vy = sin(lambda) * cos(phi);
431
            Vz = sin(phi);
432
            // TODO: Best way to 'discard' a vertex
433
            if ((({radius_g} - Vx) * Vx - Vy * Vy - Vz * Vz * {radius_p_inv2}) < 0.) {{
434
               return vec4(1. / 0., 1. / 0., pos.z, pos.w);
435
            }}
436
            tmp = {radius_g} - Vx;
437
            if ({flip_axis}) {{
438
                x = {a} * {radius_g_1} * atan(Vy / hypot(Vz, tmp));
439
                y = {a} * {radius_g_1} * atan(Vz / tmp);
440
            }}
441
            else {{
442
                x = {a} * {radius_g_1} * atan(Vy / tmp);
443
                y = {a} * {radius_g_1} * atan(Vz / hypot(Vy, tmp));
444
            }}
445
            return vec4(x, y, pos.z, pos.w);
446
        }}""",
447
        """vec4 geos_imap_e(vec4 pos) {{
448
            float a, b, k, det, x, y, Vx, Vy, Vz, lambda, phi;
449
            x = pos.x / {a};
450
            y = pos.y / {a};
451

452
            Vx = -1.0;
453
            if ({flip_axis}) {{
454
                Vz = tan(y / {radius_g_1});
455
                Vy = tan(x / {radius_g_1}) * hypot(1.0, Vz);
456
            }} else {{
457
                Vy = tan(x / {radius_g_1});
458
                Vz = tan(y / {radius_g_1}) * hypot(1.0, Vy);
459
            }}
460

461
            a = Vz / {radius_p};
462
            a = Vy * Vy + a * a + Vx * Vx;
463
            b = 2 * {radius_g} * Vx;
464
            det = ((b * b) - 4 * a * {C});
465
            if (det < 0.) {{
466
                // FIXME
467
                return vec4(1. / 0., 1. / 0., pos.z, pos.w);
468
            }}
469

470
            k = (-b - sqrt(det)) / (2. * a);
471
            Vx = {radius_g} + k * Vx;
472
            Vy *= k;
473
            Vz *= k;
474

475
            // atan2 in C
476
            lambda = atan(Vy, Vx);
477
            {over}
478
            phi = atan(Vz * cos(lambda) / Vx);
479
            phi = atan({radius_p_inv2} * tan(phi));
480
            return vec4(degrees(lambda) + {lon_0}, degrees(phi), pos.z, pos.w);
481
        }}""",
482
        """vec4 geos_imap_s(vec4 pos) {{
483
            float x, y, Vx, Vy, Vz, a, b, k, det, lambda, phi;
484
            x = pos.x / {a};
485
            y = pos.y / {a};
486
            Vx = -1.;
487
            if ({flip_axis}) {{
488
                Vz = tan(y / ({radius_g} - 1.));
489
                Vy = tan(x / ({radius_g} - 1.)) * sqrt(1. + Vz * Vz);
490
            }}
491
            else {{
492
                Vy = tan(x / ({radius_g} - 1.));
493
                Vz = tan(y / ({radius_g} - 1.)) * sqrt(1. + Vy * Vy);
494
            }}
495
            a = Vy * Vy + Vz * Vz + Vx * Vx;
496
            b = 2 * {radius_g} * Vx;
497
            det = b * b - 4 * a * {C};
498
            if (det < 0.) {{
499
                return vec4(1. / 0., 1. / 0., pos.z, pos.w);
500
            }}
501
            k = (-b - sqrt(det)) / (2 * a);
502
            Vx = {radius_g} + k * Vx;
503
            Vy *= k;
504
            Vz *= k;
505
            lambda = atan(Vy, Vx);
506
            {over}
507
            phi = atan(Vz * cos(lambda) / Vx);
508
            return vec4(degrees(lambda) + {lon_0}, degrees(phi), pos.z, pos.w);
509
        }}""",
510
    ),
511
    "stere": (
512
        stere_init,
513
        """vec4 stere_map_e(vec4 pos) {{
514
            float lambda, phi, coslam, sinlam, sinphi, x, y;
515
            lambda = radians(pos.x - {lon_0});
516
            {over}
517
            phi = radians(pos.y);
518
            coslam = cos(lambda);
519
            sinlam = sin(lambda);
520
            sinphi = sin(phi);
521
            if ({mode} == 1) {{
522
                phi = -phi;
523
                coslam = - coslam;
524
                sinphi = -sinphi;
525
            }}
526
            x = {akm1} * pj_tsfn(phi, sinphi, {e});
527
            y = {a} * -x * coslam;
528
            x *= {a} * sinlam;
529
            return vec4(x, y, pos.z, pos.w);
530
        }}""",
531
        """vec4 stere_map_s(vec4 pos) {{
532
            float lambda, phi, coslam, sinlam, x, y;
533
            lambda = radians(pos.x - {lon_0});
534
            {over}
535
            phi = radians(pos.y);
536
            coslam = cos(lambda);
537
            sinlam = sin(lambda);
538
            if ({mode} == 0) {{
539
                coslam = - coslam;
540
                phi = - phi;
541
            }}
542
            if (abs(phi - M_HALFPI) < 1.e-8) {{
543
                return vec4(1. / 0., 1. / 0., pos.z, pos.w);
544
            }}
545
            y = {akm1} * tan(M_FORTPI + .5 * phi);
546
            x = {a} * sinlam * y;
547
            y *= {a} * coslam;
548
            return vec4(x, y, pos.z, pos.w);
549
        }}""",
550
        """vec4 stere_imap_e(vec4 pos) {{
551
            float x, y, phi, lambda, tp, phi_l, sinphi;
552
            x = pos.x / {a};
553
            y = pos.y / {a};
554
            phi = radians(y);
555
            lambda = radians(x);
556
            tp = -hypot(x,y) / {akm1};
557
            phi_l = M_HALFPI - 2. * atan(tp);
558
            sinphi = 0.;
559
            if ({mode} == 0) {{
560
                y = -y;
561
            }}
562
            for (int i = 8; i-- > 0; phi_l = phi) {{
563
                sinphi = {e} * sin(phi_l);
564
                phi = 2. * atan(tp * pow((1. + sinphi) / (1. - sinphi), -.5 * {e})) + M_HALFPI;
565
                if (abs(phi_l - phi) < 1.e-10) {{
566
                    if ({mode} == 1) {{
567
                        phi = -phi;
568
                    }}
569
                    lambda = (x == 0. && y == 0.) ? 0. : atan(x, y);
570
                    {over}
571
                    return vec4(degrees(lambda) + {lon_0}, degrees(phi), pos.z, pos.w);
572
                }}
573
            }}
574
            return vec4(1. / 0., 1. / 0., pos.z, pos.w);
575
        }}""",
576
        """vec4 stere_imap_s(vec4 pos) {{
577
            float x, y, rh, cosc, phi, lambda;
578
            x = pos.x / {a};
579
            y = pos.y / {a};
580
            rh = hypot(x, y);
581
            cosc = cos(2. * atan(rh / {akm1}));
582
            phi = 0;
583
            if ({mode} == 0) {{
584
                y = -y;
585
            }}
586
            if (abs(rh) < 1.e-10) {{
587
                phi = radians({lat_0});
588
            }}
589
            else {{
590
                phi = asin({mode} == 1 ? -cosc : cosc);
591
            }}
592
            lambda = (x == 0. && y == 0.) ? 0. : atan(x, y);
593
            {over}
594
            return vec4(degrees(lambda) + {lon_0}, degrees(phi), pos.z, pos.w);
595
        }}""",
596
    ),
597
    "eqc": (
598
        eqc_init,
599
        None,
600
        """vec4 eqc_map_s(vec4 pos) {{
601
        {es};
602
        float lambda = radians(pos.x);
603
        {over}
604
        float phi = radians(pos.y);
605
        float x = {a} * {rc} * lambda;
606
        float y = {a} * (phi - {phi0});
607
        return vec4(x, y, pos.z, pos.w);
608
    }}""",
609
        None,
610
        """vec4 eqc_imap_s(vec4 pos) {{
611
            float x = pos.x / {a};
612
            float y = pos.y / {a};
613
            float lambda = x / {rc};
614
            {over}
615
            float phi = y + {phi0};
616
            return vec4(degrees(lambda), degrees(phi), pos.z, pos.w);
617
        }}""",
618
    ),
619
}
620
PROJECTIONS["lcc"] = (
2✔
621
    lcc_init,
622
    PROJECTIONS["lcc"][1],
623
    PROJECTIONS["lcc"][1],
624
    PROJECTIONS["lcc"][3],
625
    PROJECTIONS["lcc"][3],
626
)
627

628
# Misc GLSL functions used in one or more mapping functions above
629
adjlon_func = Function(
2✔
630
    """
631
    float adjlon(float lon) {
632
        if (abs(lon) <= M_PI) return (lon);
633
        lon += M_PI; // adjust to 0..2pi rad
634
        lon -= M_TWOPI * floor(lon / M_TWOPI); // remove integral # of 'revolutions'
635
        lon -= M_PI;  // adjust back to -pi..pi rad
636
        return( lon );
637
    }
638
    """
639
)
640

641
# handle prime meridian shifts
642
pm_func_str = """
2✔
643
    float adjlon(float lon) {{
644
        return lon + radians({pm});
645
    }}
646
"""
647

648
pj_msfn = Function(
2✔
649
    """
650
    float pj_msfn(float sinphi, float cosphi, float es) {
651
        return (cosphi / sqrt (1. - es * sinphi * sinphi));
652
    }
653
    """
654
)
655

656

657
def pj_msfn_py(sinphi, cosphi, es):
2✔
658
    return cosphi / np.sqrt(1.0 - es * sinphi * sinphi)
×
659

660

661
pj_tsfn = Function(
2✔
662
    """
663
    float pj_tsfn(float phi, float sinphi, float e) {
664
        sinphi *= e;
665
        return (tan (.5 * (M_HALFPI - phi)) /
666
           pow((1. - sinphi) / (1. + sinphi), .5 * e));
667
    }
668
    """
669
)
670

671

672
def pj_tsfn_py(phi, sinphi, e):
2✔
673
    sinphi *= e
×
674
    return np.tan(0.5 * (M_HALFPI - phi)) / pow((1.0 - sinphi) / (1.0 + sinphi), 0.5 * e)
×
675

676

677
pj_phi2 = Function(
2✔
678
    """
679
    float pj_phi2(float ts, float e) {
680
        float eccnth, Phi, con, dphi;
681

682
        eccnth = .5 * e;
683
        Phi = M_HALFPI - 2. * atan (ts);
684
        for (int i=15; i >= 0; --i) {
685
            con = e * sin(Phi);
686
            dphi = M_HALFPI - 2. * atan(ts * pow((1. - con) / (1. + con), eccnth)) - Phi;
687
            Phi += dphi;
688
            if (abs(dphi) <= 1.0e-10) {
689
                break;
690
            }
691
        }
692
        //if (i <= 0)
693
        //    pj_ctx_set_errno( ctx, -18 );
694
        return Phi;
695
    }
696
    """
697
)
698

699
hypot = Function(
2✔
700
    """
701
float hypot(float x, float y) {
702
    if ( x < 0.)
703
        x = -x;
704
    else if (x == 0.)
705
        return (y < 0. ? -y : y);
706
    if (y < 0.)
707
        y = -y;
708
    else if (y == 0.)
709
        return (x);
710
    if ( x < y ) {
711
        x /= y;
712
        return ( y * sqrt( 1. + x * x ) );
713
    } else {
714
        y /= x;
715
        return ( x * sqrt( 1. + y * y ) );
716
    }
717
}
718
"""
719
)
720

721

722
class PROJ4Transform(BaseTransform):
2✔
723
    glsl_map = None
2✔
724

725
    glsl_imap = None
2✔
726

727
    # Flags used to describe the transformation. Subclasses should define each
728
    # as True or False.
729
    # (usually used for making optimization decisions)
730

731
    # If True, then for any 3 colinear points, the
732
    # transformed points will also be colinear.
733
    Linear = False
2✔
734

735
    # The transformation's effect on one axis is independent
736
    # of the input position along any other axis.
737
    Orthogonal = False
2✔
738

739
    # If True, then the distance between two points is the
740
    # same as the distance between the transformed points.
741
    NonScaling = False
2✔
742

743
    # Scale factors are applied equally to all axes.
744
    Isometric = False
2✔
745

746
    def __init__(self, proj4_str, inverse=False):
2✔
UNCOV
747
        self.proj4_str = proj4_str
×
UNCOV
748
        self.proj = Proj(proj4_str)
×
UNCOV
749
        self._proj4_inverse = inverse
×
UNCOV
750
        proj_dict = self._create_proj_dict(proj4_str)
×
751

752
        # Get the specific functions for this projection
UNCOV
753
        proj_funcs = PROJECTIONS[proj_dict["proj"]]
×
754
        # set default function parameters
UNCOV
755
        proj_init = proj_funcs[0]
×
UNCOV
756
        proj_args = proj_init(proj_dict)
×
757

UNCOV
758
        if "pm" in proj_args:
×
759
            # force to float
760
            proj_args["pm"] = float(proj_args["pm"])
×
761
            proj_args["over"] = "lambda = adjlon(lambda);"
×
UNCOV
762
        elif proj_args.get("over"):
×
763
            proj_args["over"] = ""
×
764
        else:
UNCOV
765
            proj_args["over"] = "lambda = adjlon(lambda);"
×
766

UNCOV
767
        if proj_dict["a"] == proj_dict["b"]:
×
768
            # spheroid
769
            self.glsl_map = proj_funcs[2]
×
770
            self.glsl_imap = proj_funcs[4]
×
771
            if self.glsl_map is None or self.glsl_imap is None:
×
772
                raise ValueError("Spheroid transform for {} not implemented yet".format(proj_dict["proj"]))
×
773
        else:
774
            # ellipsoid
UNCOV
775
            self.glsl_map = proj_funcs[1]
×
UNCOV
776
            self.glsl_imap = proj_funcs[3]
×
UNCOV
777
            if self.glsl_map is None or self.glsl_imap is None:
×
778
                raise ValueError("Ellipsoid transform for {} not implemented yet".format(proj_dict["proj"]))
×
779

UNCOV
780
        self.glsl_map = self.glsl_map.format(**proj_args)
×
UNCOV
781
        self.glsl_imap = self.glsl_imap.format(**proj_args)
×
782

UNCOV
783
        if self._proj4_inverse:
×
784
            self.glsl_map, self.glsl_imap = self.glsl_imap, self.glsl_map
×
785

UNCOV
786
        super(PROJ4Transform, self).__init__()
×
787

788
        # Add common definitions and functions
UNCOV
789
        for d in math_consts + COMMON_VALUES + [pj_tsfn, pj_phi2, hypot]:
×
UNCOV
790
            self._shader_map._add_dep(d)
×
UNCOV
791
            self._shader_imap._add_dep(d)
×
792

UNCOV
793
        if "pm" in proj_args:
×
794
            pm_func = Function(pm_func_str.format(**proj_args))
×
795
            self._shader_map._add_dep(pm_func)
×
796
            self._shader_imap._add_dep(pm_func)
×
UNCOV
797
        elif proj_args["over"]:
×
UNCOV
798
            self._shader_map._add_dep(adjlon_func)
×
UNCOV
799
            self._shader_imap._add_dep(adjlon_func)
×
800

801
        # Add special handling of possible infinity lon/lat values
UNCOV
802
        self._shader_map[
×
803
            "pre"
804
        ] = """
805
    if (abs(pos.x) > 1e30 || abs(pos.y) > 1e30)
806
        return vec4(1. / 0., 1. / 0., pos.z, pos.w);
807
        """
808

809
        # print(self._shader_map.compile())
810

811
    @property
2✔
812
    def is_geographic(self):
2✔
813
        if hasattr(self.proj, "crs"):
×
814
            # pyproj 2.0+
815
            return self.proj.crs.is_geographic
×
816
        return self.proj.is_latlong()
×
817

818
    def _create_proj_dict(self, proj_str):  # noqa: C901
2✔
UNCOV
819
        d = tuple(x.replace("+", "").split("=") for x in proj_str.split(" "))
×
UNCOV
820
        d = dict((x[0], x[1] if len(x) > 1 else "true") for x in d)
×
821

822
        # convert numerical parameters to floats
UNCOV
823
        for k in d.keys():
×
UNCOV
824
            try:
×
UNCOV
825
                d[k] = float(d[k])
×
UNCOV
826
            except ValueError:
×
UNCOV
827
                pass
×
828

UNCOV
829
        d["proj4_str"] = proj_str
×
830

831
        # if they haven't provided a radius then they must have provided a datum or ellps
UNCOV
832
        if "R" in d:
×
833
            # spheroid
834
            d.setdefault("a", d["R"])
×
835
            d.setdefault("b", d["R"])
×
UNCOV
836
        if "a" not in d:
×
UNCOV
837
            if "datum" not in d:
×
UNCOV
838
                d.setdefault("ellps", d.setdefault("datum", "WGS84"))
×
839
            else:
840
                d.setdefault("ellps", d.get("datum"))
×
841

842
        # if they provided an ellps/datum fill in information we know about it
UNCOV
843
        if d.get("ellps") is not None:
×
844
            # get information on the ellps being used
UNCOV
845
            ellps_info = pj_ellps[d["ellps"]]
×
UNCOV
846
            for k in ["a", "b", "rf"]:
×
UNCOV
847
                if k in ellps_info:
×
UNCOV
848
                    d.setdefault(k, ellps_info[k])
×
849

850
        # derive b, es, f, e
UNCOV
851
        if "rf" not in d:
×
852
            if "f" in d:
×
853
                d["rf"] = 1.0 / d["f"]
×
854
            elif d["a"] == d["b"]:
×
855
                d["rf"] = 0.0
×
856
            else:
857
                d["rf"] = d["a"] / (d["a"] - d["b"])
×
UNCOV
858
        if "f" not in d:
×
UNCOV
859
            if d["rf"]:
×
UNCOV
860
                d["f"] = 1.0 / d["rf"]
×
861
            else:
862
                d["f"] = 0.0
×
UNCOV
863
        if "b" not in d:
×
864
            # a and rf must be in the dict
UNCOV
865
            d["b"] = d["a"] * (1.0 - d["f"])
×
UNCOV
866
        if "es" not in d:
×
UNCOV
867
            if "e" in d:
×
868
                d["es"] = d["e"] ** 2
×
869
            else:
UNCOV
870
                d["es"] = 2 * d["f"] - d["f"] ** 2
×
UNCOV
871
        if "e" not in d:
×
UNCOV
872
            d["e"] = d["es"] ** 0.5
×
873

UNCOV
874
        return d
×
875

876
    @arg_to_vec4
2✔
877
    def map(self, coords):
2✔
878
        """Map coordinates
879

880
        Parameters
881
        ----------
882
        coords : array-like
883
            Coordinates to map.
884
        """
885
        m = np.empty(coords.shape)
×
886
        if self.is_geographic:
×
887
            m[:, 0] = coords[:, 0]
×
888
            m[:, 1] = coords[:, 1]
×
889
        else:
890
            m[:, 0], m[:, 1] = self.proj(coords[:, 0], coords[:, 1], inverse=self._proj4_inverse)
×
891
        m[:, 2:] = coords[:, 2:]
×
892
        return m
×
893

894
    @arg_to_vec4
2✔
895
    def imap(self, coords):
2✔
896
        """Inverse map coordinates
897

898
        Parameters
899
        ----------
900
        coords : array-like
901
            Coordinates to inverse map.
902
        """
903
        m = np.empty(coords.shape)
×
904
        if self.is_geographic:
×
905
            m[:, 0] = coords[:, 0]
×
906
            m[:, 1] = coords[:, 1]
×
907
        else:
908
            m[:, 0], m[:, 1] = self.proj(coords[:, 0], coords[:, 1], inverse=not self._proj4_inverse)
×
909
        m[:, 2:] = coords[:, 2:]
×
910
        return m
×
911

912
    def __repr__(self):
2✔
913
        return "<%s:%s at 0x%x>" % (self.__class__.__name__, self.proj4_str, id(self))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc