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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
|
%global _empty_manifest_terminate_build 0
Name: python-convertbng
Version: 0.6.41
Release: 1
Summary: Fast lon, lat to and from ETRS89 and BNG (OSGB36) using the OS OSTN15 transform via Rust FFI
License: MIT
URL: https://github.com/urschrei/convertbng
Source0: https://mirrors.nju.edu.cn/pypi/web/packages/b0/48/b8da0166ef44d27eae0246782cc44cac481d6c91a6ac5f05d392c0d1a6d0/convertbng-0.6.41.tar.gz
Requires: python3-numpy
Requires: python3-cython
Requires: python3-pytest
Requires: python3-pytest
%description
[](https://github.com/urschrei/convertbng/actions/workflows/wheels.yml) [](https://coveralls.io/github/urschrei/convertbng?branch=master) [](https://pypi.python.org/pypi/convertbng) [](license.txt) [](https://pepy.tech/project/convertbng)[](https://zenodo.org/badge/latestdoi/37950596)
# Description
A utility library for converting decimal [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) longitude and latitude coordinates into ETRS89 ([EPSG:25830](http://spatialreference.org/ref/epsg/etrs89-utm-zone-30n/)) and/or British National Grid (More correctly: OSGB36, or [EPSG:27700](http://spatialreference.org/ref/epsg/osgb-1936-british-national-grid/)) Eastings and Northings, and vice versa.
Conversion is handled by a [Rust binary](https://github.com/urschrei/rust_bng) using FFI, and is quite fast. Some benchmarks can be found [here](https://github.com/urschrei/lonlat_bng#benchmark).
# Installation
`pip install convertbng`
Please use an up-to-date version of pip (`8.1.2` as of June 2016)
## Supported Platforms
The package has been built for and tested on the following platforms:
- Linux x86_64 and aarch64 Python 3.{7, 8, 9, 10, 11} (Manylinux2014)
- macOS x86_64 and arm64 Python 3.{7, 8, 9, 10, 11}
- Windows 64-bit Python 3.{7, 8, 9, 10, 11}
### Windows Binaries
The Rust DLL and the Cython extension used by this package have been built with an MSVC toolchain. You shouldn't need to install any additional runtimes in order for the wheel to work, but please open an issue if you encounter any errors.
# Usage
The functions accept either a sequence (such as a list or numpy array) of longitude or easting values and a sequence of latitude or northing values, **or** a single longitude/easting value and single latitude/northing value. Note the return type:
`"returns a list of two lists containing floats, respectively"`
**NOTE**: Coordinate pairs outside the BNG bounding box, or without OSTN15 coverage will return a result of
`[[nan], [nan]]`, which cannot be mapped. Since transformed coordinates are guaranteed to be returned in the same order as the input, it is trivial to check for this value. Alternatively, ensure your data fall within the bounding box before transforming them:
**Longitude**:
East: 1.7800
West: -7.5600
**Latitude**:
North: 60.8400
South: 49.9600
All functions try to be liberal about what containers they accept: `list`, `tuple`, `array.array`, `numpy.ndarray`, and pretty much anything that has the `__iter__` attribute should work, including generators.
```python
from convertbng.util import convert_bng, convert_lonlat
# convert a single value
res = convert_bng(lon, lat)
# convert longitude and latitude to OSGB36 Eastings and Northings using OSTN15 corrections
lons = [lon1, lon2, lon3]
lats = [lat1, lat2, lat3]
res_list = convert_bng(lons, lats)
# convert lists of BNG Eastings and Northings to longitude, latitude
eastings = [easting1, easting2, easting3]
northings = [northing1, northing2, northing3]
res_list_en = convert_lonlat(eastings, northings)
# assumes numpy imported as np
lons_np = np.array(lons)
lats_np = np.array(lats)
res_list_np = convert_bng(lons_np, lats_np)
```
# Cython Module
If you're comfortable with restricting yourself to `NumPy f64` arrays, you may use the Cython functions instead. These are identical to those listed below, but performance on large datasets is better. They are selected by changing the import statement
`from convertbng.util import` to
**`from convertbng.cutil import`**
The conversion functions will accept most sequences which implement `__iter__`, as above (`list`, `tuple`, `float`, `array.array`, `numpy.ndarray`), but **will always return `NumPy f64 ndarray`**. In addition, you must ensure that your inputs are `float`, `f64`, or `d` in the case of `array.array`.
## But I Have a List of Coordinate Pairs
```python
coords = [[1.0, 2.0], [3.0, 4.0]]
a, b = list(zip(*coords))
# a is (1.0, 3.0)
# b is (2.0, 4.0)
```
### But I have `Shapely` Geometries
```python
from convertbng.util import convert_etrs89_to_ll
from shapely.geometry import LineString
from shapely.ops import transform
from math import isnan
from functools import partial
def transform_protect_nan(f, xs, ys):
# This function protects Shapely against NaN values in the output of the
# transform, which would otherwise case a segfault.
xs_t, ys_t = f(xs, ys)
assert not any([isnan(x) for x in xs_t]), "Transformed xs contains NaNs"
assert not any([isnan(y) for y in ys_t]), "Transformed ys contains NaNs"
return xs_t, ys_t
convert_etrs89_to_lonlat_protect_nan = partial(transform_protect_nan, convert_etrs89_to_ll)
line = LineString([[651307.003, 313255.686], [651307.004, 313255.687]])
new_line = transform(convert_etrs89_to_lonlat_protect_nan, line)
```
# Available Conversions (AKA I Want To…)
- transform longitudes and latitudes to OSGB36 Eastings and Northings **very accurately**:
- use `convert_bng()`
- transform OSGB36 Eastings and Northings to longitude and latitude, **very accurately**:
- use `convert_lonlat()`
- transform longitudes and latitudes to ETRS89 Eastings and Northings, **very quickly** (without OSTN15 corrections):
- use `convert_to_etrs89()`
- transform ETRS89 Eastings and Northings to ETRS89 longitude and latitude, **very quickly** (the transformation does not use OSTN15):
- use `convert_etrs89_to_lonlat()`
- convert ETRS89 Eastings and Northings to their most accurate real-world representation, using the OSTN15 corrections:
- use `convert_etrs89_to_osgb36()`
Provided for completeness:
- transform accurate OSGB36 Eastings and Northings to *less-accurate* ETRS89 Eastings and Northings:
- use `convert_osgb36_to_etrs89()`
# Relationship between ETRS89 and WGS84
From [Transformations and OSGM02™ User guide](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/formats-for-developers.html), p7. Emphasis mine.
>[…] ETRS89 is a precise version of the better known WGS84 reference system optimised for use in Europe; **however, for most purposes it can be considered equivalent to WGS84**.
Specifically, the motion of the European continental plate is not apparent in ETRS89, which allows a fixed relationship to be established between this system and Ordnance Survey mapping coordinate systems.
Additional precise versions of WGS84 are currently in use, notably ITRS; these are not equivalent to ETRS89. The difference between ITRS and ETRS89 is in the order of 0.25 m (in 1999), and growing by 0.025 m per year in UK and Ireland. This effect is only relevant in international scientific applications. **For all navigation, mapping, GIS, and engineering applications within the tectonically stable parts of Europe (including UK and Ireland), the term ETRS89 should be taken as synonymous with WGS84**.
In essence, this means that anywhere you see ETRS89 in this README, you can substitute WGS84.
## What CRS Are My Data In
- if you have latitude and longitude coordinates:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you got your coordinates from a smartphone or a consumer GPS:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you have x and y coordinates, or you got your coordinates from Google Maps or Bing Maps and they look something like `(-626172.1357121646, 6887893.4928337997)`, or the phrase "Spherical Mercator" is mentioned anywhere:
- they're probably in [Web Mercator](http://spatialreference.org/ref/sr-org/6864/). You **must** convert them to WGS84 first. Use `convert_epsg3857_to_wgs84([x_coordinates], [y_coordinates])` to do so.
# Accuracy
`convert_bng` and `convert_lonlat` first use the standard seven-step [Helmert transform](https://en.wikipedia.org/wiki/Helmert_transformation) to convert coordinates. This is fast, but not particularly accurate – it can introduce positional error up to approximately 5 metres. For most applications, this is not of particular concern – the input data (especially those originating with smartphone GPS) probably exceed this level of error in any case. In order to adjust for this, the OSTN15 adjustments for the kilometer-grid the ETRS89 point falls in are retrieved, and a linear interpolation to give final, accurate coordinates is carried out. This process happens in reverse for `convert_lonlat`.
## OSTN15
[OSTN15](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html) data are used for highly accurate conversions from ETRS89 latitude and longitude, or ETRS89 Eastings and Northings to OSGB36 Eastings and Northings, and vice versa. These data will usually have been recorded using the [National GPS Network](https://www.ordnancesurvey.co.uk/business-and-government/products/os-net/index.html):
### Accuracy of *Your* Data
Conversion of your coordinates using OSTN15 transformations will be accurate, but if you're using consumer equipment, or got your data off the web, be aware that you're converting coordinates which probably weren't accurately recorded in the first place. That's because [accurate surveying is difficult](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html).
### Accuracy of the OSTN15 transformation used in this library
- ETRS89 longitude and latitude / Eastings and Northings to OSGB36 conversion agrees with the provided Ordnance Survey test data in **39 of the 40** test coordinates (excluding two coordinates designed to return no data; these correctly fail).
- The only discrepancy – in point `TP31`– is **1mm**.
- OSGB36 to ETRS89 longitude and latitude conversion is accurate to within 8 decimal places, or 1.1mm.
### A Note on Ellipsoids
WGS84 and ETRS89 coordinates use the GRS80 ellipsoid, whereas OSGB36 uses the Airy 1830 ellipsoid, which provides a regional best fit for Britain. Positions for coordinates in Great Britain can differ by over 100m as a result. It is thus inadvisable to attempt calculations using mixed ETRS89 and OSGB36 coordinates.
[]( "OSTN15")
## Implementation
The main detail of interest is the FFI interface between Python and Rust, the Python side of which can be found in [util.py](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L64-L100) (the `ctypes` implementation), [cutil.pyx](https://github.com/urschrei/convertbng/blob/master/convertbng/cutil.pyx#L51-L86) (the `cython` implementation), and the Rust side of which can be found in [ffi.rs](https://github.com/urschrei/rust_bng/blob/master/src/ffi.rs#L47-L271).
The [ctypes](https://docs.python.org/2/library/ctypes.html) library expects C-compatible data structures, which we define in Rust (see above). We then define methods which allow us to receive, safely access, return, and free data across the FFI boundary.
Finally, we link the Rust conversion functions from `util.py` [again](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L103-L205). Note the `errcheck` assignments, which convert the FFI-compatible ctypes data structures to tuple lists.
# Building the binary for local development
- ensure you have Rust 1.x and Cargo [installed](https://www.rustup.rs)
- clone https://github.com/urschrei/lonlat_bng, and ensure it's adjacent to this dir (i.e. `code/witnessme/convertbng` and `code/witnessme/rust_bng`)
- in this dir, run `make clean` then `make`
# Tests
You can run the Python module tests by running "make test".
Tests require both `numpy` and `nose`.
# License
[MIT](license.txt)
## Citing `Convertbng`
If Convertbng has been significant in your research, and you would like to acknowledge the project in your academic publication, we suggest citing it as follows (example in APA style, 7th edition):
> Hügel, S. (2021). Convertbng (Version X.Y.Z) [Computer software]. https://doi.org/10.5281/zenodo.5774931
In Bibtex format:
@software{Hugel_Convertbng_2021,
author = {Hügel, Stephan},
doi = {10.5281/zenodo.5774931},
license = {MIT},
month = {12},
title = {{Convertbng}},
url = {https://github.com/urschrei/convertbng},
version = {X.Y.Z},
year = {2021}
}
%package -n python3-convertbng
Summary: Fast lon, lat to and from ETRS89 and BNG (OSGB36) using the OS OSTN15 transform via Rust FFI
Provides: python-convertbng
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: python3-pip
BuildRequires: python3-cffi
BuildRequires: gcc
BuildRequires: gdb
%description -n python3-convertbng
[](https://github.com/urschrei/convertbng/actions/workflows/wheels.yml) [](https://coveralls.io/github/urschrei/convertbng?branch=master) [](https://pypi.python.org/pypi/convertbng) [](license.txt) [](https://pepy.tech/project/convertbng)[](https://zenodo.org/badge/latestdoi/37950596)
# Description
A utility library for converting decimal [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) longitude and latitude coordinates into ETRS89 ([EPSG:25830](http://spatialreference.org/ref/epsg/etrs89-utm-zone-30n/)) and/or British National Grid (More correctly: OSGB36, or [EPSG:27700](http://spatialreference.org/ref/epsg/osgb-1936-british-national-grid/)) Eastings and Northings, and vice versa.
Conversion is handled by a [Rust binary](https://github.com/urschrei/rust_bng) using FFI, and is quite fast. Some benchmarks can be found [here](https://github.com/urschrei/lonlat_bng#benchmark).
# Installation
`pip install convertbng`
Please use an up-to-date version of pip (`8.1.2` as of June 2016)
## Supported Platforms
The package has been built for and tested on the following platforms:
- Linux x86_64 and aarch64 Python 3.{7, 8, 9, 10, 11} (Manylinux2014)
- macOS x86_64 and arm64 Python 3.{7, 8, 9, 10, 11}
- Windows 64-bit Python 3.{7, 8, 9, 10, 11}
### Windows Binaries
The Rust DLL and the Cython extension used by this package have been built with an MSVC toolchain. You shouldn't need to install any additional runtimes in order for the wheel to work, but please open an issue if you encounter any errors.
# Usage
The functions accept either a sequence (such as a list or numpy array) of longitude or easting values and a sequence of latitude or northing values, **or** a single longitude/easting value and single latitude/northing value. Note the return type:
`"returns a list of two lists containing floats, respectively"`
**NOTE**: Coordinate pairs outside the BNG bounding box, or without OSTN15 coverage will return a result of
`[[nan], [nan]]`, which cannot be mapped. Since transformed coordinates are guaranteed to be returned in the same order as the input, it is trivial to check for this value. Alternatively, ensure your data fall within the bounding box before transforming them:
**Longitude**:
East: 1.7800
West: -7.5600
**Latitude**:
North: 60.8400
South: 49.9600
All functions try to be liberal about what containers they accept: `list`, `tuple`, `array.array`, `numpy.ndarray`, and pretty much anything that has the `__iter__` attribute should work, including generators.
```python
from convertbng.util import convert_bng, convert_lonlat
# convert a single value
res = convert_bng(lon, lat)
# convert longitude and latitude to OSGB36 Eastings and Northings using OSTN15 corrections
lons = [lon1, lon2, lon3]
lats = [lat1, lat2, lat3]
res_list = convert_bng(lons, lats)
# convert lists of BNG Eastings and Northings to longitude, latitude
eastings = [easting1, easting2, easting3]
northings = [northing1, northing2, northing3]
res_list_en = convert_lonlat(eastings, northings)
# assumes numpy imported as np
lons_np = np.array(lons)
lats_np = np.array(lats)
res_list_np = convert_bng(lons_np, lats_np)
```
# Cython Module
If you're comfortable with restricting yourself to `NumPy f64` arrays, you may use the Cython functions instead. These are identical to those listed below, but performance on large datasets is better. They are selected by changing the import statement
`from convertbng.util import` to
**`from convertbng.cutil import`**
The conversion functions will accept most sequences which implement `__iter__`, as above (`list`, `tuple`, `float`, `array.array`, `numpy.ndarray`), but **will always return `NumPy f64 ndarray`**. In addition, you must ensure that your inputs are `float`, `f64`, or `d` in the case of `array.array`.
## But I Have a List of Coordinate Pairs
```python
coords = [[1.0, 2.0], [3.0, 4.0]]
a, b = list(zip(*coords))
# a is (1.0, 3.0)
# b is (2.0, 4.0)
```
### But I have `Shapely` Geometries
```python
from convertbng.util import convert_etrs89_to_ll
from shapely.geometry import LineString
from shapely.ops import transform
from math import isnan
from functools import partial
def transform_protect_nan(f, xs, ys):
# This function protects Shapely against NaN values in the output of the
# transform, which would otherwise case a segfault.
xs_t, ys_t = f(xs, ys)
assert not any([isnan(x) for x in xs_t]), "Transformed xs contains NaNs"
assert not any([isnan(y) for y in ys_t]), "Transformed ys contains NaNs"
return xs_t, ys_t
convert_etrs89_to_lonlat_protect_nan = partial(transform_protect_nan, convert_etrs89_to_ll)
line = LineString([[651307.003, 313255.686], [651307.004, 313255.687]])
new_line = transform(convert_etrs89_to_lonlat_protect_nan, line)
```
# Available Conversions (AKA I Want To…)
- transform longitudes and latitudes to OSGB36 Eastings and Northings **very accurately**:
- use `convert_bng()`
- transform OSGB36 Eastings and Northings to longitude and latitude, **very accurately**:
- use `convert_lonlat()`
- transform longitudes and latitudes to ETRS89 Eastings and Northings, **very quickly** (without OSTN15 corrections):
- use `convert_to_etrs89()`
- transform ETRS89 Eastings and Northings to ETRS89 longitude and latitude, **very quickly** (the transformation does not use OSTN15):
- use `convert_etrs89_to_lonlat()`
- convert ETRS89 Eastings and Northings to their most accurate real-world representation, using the OSTN15 corrections:
- use `convert_etrs89_to_osgb36()`
Provided for completeness:
- transform accurate OSGB36 Eastings and Northings to *less-accurate* ETRS89 Eastings and Northings:
- use `convert_osgb36_to_etrs89()`
# Relationship between ETRS89 and WGS84
From [Transformations and OSGM02™ User guide](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/formats-for-developers.html), p7. Emphasis mine.
>[…] ETRS89 is a precise version of the better known WGS84 reference system optimised for use in Europe; **however, for most purposes it can be considered equivalent to WGS84**.
Specifically, the motion of the European continental plate is not apparent in ETRS89, which allows a fixed relationship to be established between this system and Ordnance Survey mapping coordinate systems.
Additional precise versions of WGS84 are currently in use, notably ITRS; these are not equivalent to ETRS89. The difference between ITRS and ETRS89 is in the order of 0.25 m (in 1999), and growing by 0.025 m per year in UK and Ireland. This effect is only relevant in international scientific applications. **For all navigation, mapping, GIS, and engineering applications within the tectonically stable parts of Europe (including UK and Ireland), the term ETRS89 should be taken as synonymous with WGS84**.
In essence, this means that anywhere you see ETRS89 in this README, you can substitute WGS84.
## What CRS Are My Data In
- if you have latitude and longitude coordinates:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you got your coordinates from a smartphone or a consumer GPS:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you have x and y coordinates, or you got your coordinates from Google Maps or Bing Maps and they look something like `(-626172.1357121646, 6887893.4928337997)`, or the phrase "Spherical Mercator" is mentioned anywhere:
- they're probably in [Web Mercator](http://spatialreference.org/ref/sr-org/6864/). You **must** convert them to WGS84 first. Use `convert_epsg3857_to_wgs84([x_coordinates], [y_coordinates])` to do so.
# Accuracy
`convert_bng` and `convert_lonlat` first use the standard seven-step [Helmert transform](https://en.wikipedia.org/wiki/Helmert_transformation) to convert coordinates. This is fast, but not particularly accurate – it can introduce positional error up to approximately 5 metres. For most applications, this is not of particular concern – the input data (especially those originating with smartphone GPS) probably exceed this level of error in any case. In order to adjust for this, the OSTN15 adjustments for the kilometer-grid the ETRS89 point falls in are retrieved, and a linear interpolation to give final, accurate coordinates is carried out. This process happens in reverse for `convert_lonlat`.
## OSTN15
[OSTN15](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html) data are used for highly accurate conversions from ETRS89 latitude and longitude, or ETRS89 Eastings and Northings to OSGB36 Eastings and Northings, and vice versa. These data will usually have been recorded using the [National GPS Network](https://www.ordnancesurvey.co.uk/business-and-government/products/os-net/index.html):
### Accuracy of *Your* Data
Conversion of your coordinates using OSTN15 transformations will be accurate, but if you're using consumer equipment, or got your data off the web, be aware that you're converting coordinates which probably weren't accurately recorded in the first place. That's because [accurate surveying is difficult](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html).
### Accuracy of the OSTN15 transformation used in this library
- ETRS89 longitude and latitude / Eastings and Northings to OSGB36 conversion agrees with the provided Ordnance Survey test data in **39 of the 40** test coordinates (excluding two coordinates designed to return no data; these correctly fail).
- The only discrepancy – in point `TP31`– is **1mm**.
- OSGB36 to ETRS89 longitude and latitude conversion is accurate to within 8 decimal places, or 1.1mm.
### A Note on Ellipsoids
WGS84 and ETRS89 coordinates use the GRS80 ellipsoid, whereas OSGB36 uses the Airy 1830 ellipsoid, which provides a regional best fit for Britain. Positions for coordinates in Great Britain can differ by over 100m as a result. It is thus inadvisable to attempt calculations using mixed ETRS89 and OSGB36 coordinates.
[]( "OSTN15")
## Implementation
The main detail of interest is the FFI interface between Python and Rust, the Python side of which can be found in [util.py](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L64-L100) (the `ctypes` implementation), [cutil.pyx](https://github.com/urschrei/convertbng/blob/master/convertbng/cutil.pyx#L51-L86) (the `cython` implementation), and the Rust side of which can be found in [ffi.rs](https://github.com/urschrei/rust_bng/blob/master/src/ffi.rs#L47-L271).
The [ctypes](https://docs.python.org/2/library/ctypes.html) library expects C-compatible data structures, which we define in Rust (see above). We then define methods which allow us to receive, safely access, return, and free data across the FFI boundary.
Finally, we link the Rust conversion functions from `util.py` [again](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L103-L205). Note the `errcheck` assignments, which convert the FFI-compatible ctypes data structures to tuple lists.
# Building the binary for local development
- ensure you have Rust 1.x and Cargo [installed](https://www.rustup.rs)
- clone https://github.com/urschrei/lonlat_bng, and ensure it's adjacent to this dir (i.e. `code/witnessme/convertbng` and `code/witnessme/rust_bng`)
- in this dir, run `make clean` then `make`
# Tests
You can run the Python module tests by running "make test".
Tests require both `numpy` and `nose`.
# License
[MIT](license.txt)
## Citing `Convertbng`
If Convertbng has been significant in your research, and you would like to acknowledge the project in your academic publication, we suggest citing it as follows (example in APA style, 7th edition):
> Hügel, S. (2021). Convertbng (Version X.Y.Z) [Computer software]. https://doi.org/10.5281/zenodo.5774931
In Bibtex format:
@software{Hugel_Convertbng_2021,
author = {Hügel, Stephan},
doi = {10.5281/zenodo.5774931},
license = {MIT},
month = {12},
title = {{Convertbng}},
url = {https://github.com/urschrei/convertbng},
version = {X.Y.Z},
year = {2021}
}
%package help
Summary: Development documents and examples for convertbng
Provides: python3-convertbng-doc
%description help
[](https://github.com/urschrei/convertbng/actions/workflows/wheels.yml) [](https://coveralls.io/github/urschrei/convertbng?branch=master) [](https://pypi.python.org/pypi/convertbng) [](license.txt) [](https://pepy.tech/project/convertbng)[](https://zenodo.org/badge/latestdoi/37950596)
# Description
A utility library for converting decimal [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) longitude and latitude coordinates into ETRS89 ([EPSG:25830](http://spatialreference.org/ref/epsg/etrs89-utm-zone-30n/)) and/or British National Grid (More correctly: OSGB36, or [EPSG:27700](http://spatialreference.org/ref/epsg/osgb-1936-british-national-grid/)) Eastings and Northings, and vice versa.
Conversion is handled by a [Rust binary](https://github.com/urschrei/rust_bng) using FFI, and is quite fast. Some benchmarks can be found [here](https://github.com/urschrei/lonlat_bng#benchmark).
# Installation
`pip install convertbng`
Please use an up-to-date version of pip (`8.1.2` as of June 2016)
## Supported Platforms
The package has been built for and tested on the following platforms:
- Linux x86_64 and aarch64 Python 3.{7, 8, 9, 10, 11} (Manylinux2014)
- macOS x86_64 and arm64 Python 3.{7, 8, 9, 10, 11}
- Windows 64-bit Python 3.{7, 8, 9, 10, 11}
### Windows Binaries
The Rust DLL and the Cython extension used by this package have been built with an MSVC toolchain. You shouldn't need to install any additional runtimes in order for the wheel to work, but please open an issue if you encounter any errors.
# Usage
The functions accept either a sequence (such as a list or numpy array) of longitude or easting values and a sequence of latitude or northing values, **or** a single longitude/easting value and single latitude/northing value. Note the return type:
`"returns a list of two lists containing floats, respectively"`
**NOTE**: Coordinate pairs outside the BNG bounding box, or without OSTN15 coverage will return a result of
`[[nan], [nan]]`, which cannot be mapped. Since transformed coordinates are guaranteed to be returned in the same order as the input, it is trivial to check for this value. Alternatively, ensure your data fall within the bounding box before transforming them:
**Longitude**:
East: 1.7800
West: -7.5600
**Latitude**:
North: 60.8400
South: 49.9600
All functions try to be liberal about what containers they accept: `list`, `tuple`, `array.array`, `numpy.ndarray`, and pretty much anything that has the `__iter__` attribute should work, including generators.
```python
from convertbng.util import convert_bng, convert_lonlat
# convert a single value
res = convert_bng(lon, lat)
# convert longitude and latitude to OSGB36 Eastings and Northings using OSTN15 corrections
lons = [lon1, lon2, lon3]
lats = [lat1, lat2, lat3]
res_list = convert_bng(lons, lats)
# convert lists of BNG Eastings and Northings to longitude, latitude
eastings = [easting1, easting2, easting3]
northings = [northing1, northing2, northing3]
res_list_en = convert_lonlat(eastings, northings)
# assumes numpy imported as np
lons_np = np.array(lons)
lats_np = np.array(lats)
res_list_np = convert_bng(lons_np, lats_np)
```
# Cython Module
If you're comfortable with restricting yourself to `NumPy f64` arrays, you may use the Cython functions instead. These are identical to those listed below, but performance on large datasets is better. They are selected by changing the import statement
`from convertbng.util import` to
**`from convertbng.cutil import`**
The conversion functions will accept most sequences which implement `__iter__`, as above (`list`, `tuple`, `float`, `array.array`, `numpy.ndarray`), but **will always return `NumPy f64 ndarray`**. In addition, you must ensure that your inputs are `float`, `f64`, or `d` in the case of `array.array`.
## But I Have a List of Coordinate Pairs
```python
coords = [[1.0, 2.0], [3.0, 4.0]]
a, b = list(zip(*coords))
# a is (1.0, 3.0)
# b is (2.0, 4.0)
```
### But I have `Shapely` Geometries
```python
from convertbng.util import convert_etrs89_to_ll
from shapely.geometry import LineString
from shapely.ops import transform
from math import isnan
from functools import partial
def transform_protect_nan(f, xs, ys):
# This function protects Shapely against NaN values in the output of the
# transform, which would otherwise case a segfault.
xs_t, ys_t = f(xs, ys)
assert not any([isnan(x) for x in xs_t]), "Transformed xs contains NaNs"
assert not any([isnan(y) for y in ys_t]), "Transformed ys contains NaNs"
return xs_t, ys_t
convert_etrs89_to_lonlat_protect_nan = partial(transform_protect_nan, convert_etrs89_to_ll)
line = LineString([[651307.003, 313255.686], [651307.004, 313255.687]])
new_line = transform(convert_etrs89_to_lonlat_protect_nan, line)
```
# Available Conversions (AKA I Want To…)
- transform longitudes and latitudes to OSGB36 Eastings and Northings **very accurately**:
- use `convert_bng()`
- transform OSGB36 Eastings and Northings to longitude and latitude, **very accurately**:
- use `convert_lonlat()`
- transform longitudes and latitudes to ETRS89 Eastings and Northings, **very quickly** (without OSTN15 corrections):
- use `convert_to_etrs89()`
- transform ETRS89 Eastings and Northings to ETRS89 longitude and latitude, **very quickly** (the transformation does not use OSTN15):
- use `convert_etrs89_to_lonlat()`
- convert ETRS89 Eastings and Northings to their most accurate real-world representation, using the OSTN15 corrections:
- use `convert_etrs89_to_osgb36()`
Provided for completeness:
- transform accurate OSGB36 Eastings and Northings to *less-accurate* ETRS89 Eastings and Northings:
- use `convert_osgb36_to_etrs89()`
# Relationship between ETRS89 and WGS84
From [Transformations and OSGM02™ User guide](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/formats-for-developers.html), p7. Emphasis mine.
>[…] ETRS89 is a precise version of the better known WGS84 reference system optimised for use in Europe; **however, for most purposes it can be considered equivalent to WGS84**.
Specifically, the motion of the European continental plate is not apparent in ETRS89, which allows a fixed relationship to be established between this system and Ordnance Survey mapping coordinate systems.
Additional precise versions of WGS84 are currently in use, notably ITRS; these are not equivalent to ETRS89. The difference between ITRS and ETRS89 is in the order of 0.25 m (in 1999), and growing by 0.025 m per year in UK and Ireland. This effect is only relevant in international scientific applications. **For all navigation, mapping, GIS, and engineering applications within the tectonically stable parts of Europe (including UK and Ireland), the term ETRS89 should be taken as synonymous with WGS84**.
In essence, this means that anywhere you see ETRS89 in this README, you can substitute WGS84.
## What CRS Are My Data In
- if you have latitude and longitude coordinates:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you got your coordinates from a smartphone or a consumer GPS:
- They're probably [WGS84](http://spatialreference.org/ref/epsg/wgs-84/). Everything's fine!
- if you have x and y coordinates, or you got your coordinates from Google Maps or Bing Maps and they look something like `(-626172.1357121646, 6887893.4928337997)`, or the phrase "Spherical Mercator" is mentioned anywhere:
- they're probably in [Web Mercator](http://spatialreference.org/ref/sr-org/6864/). You **must** convert them to WGS84 first. Use `convert_epsg3857_to_wgs84([x_coordinates], [y_coordinates])` to do so.
# Accuracy
`convert_bng` and `convert_lonlat` first use the standard seven-step [Helmert transform](https://en.wikipedia.org/wiki/Helmert_transformation) to convert coordinates. This is fast, but not particularly accurate – it can introduce positional error up to approximately 5 metres. For most applications, this is not of particular concern – the input data (especially those originating with smartphone GPS) probably exceed this level of error in any case. In order to adjust for this, the OSTN15 adjustments for the kilometer-grid the ETRS89 point falls in are retrieved, and a linear interpolation to give final, accurate coordinates is carried out. This process happens in reverse for `convert_lonlat`.
## OSTN15
[OSTN15](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html) data are used for highly accurate conversions from ETRS89 latitude and longitude, or ETRS89 Eastings and Northings to OSGB36 Eastings and Northings, and vice versa. These data will usually have been recorded using the [National GPS Network](https://www.ordnancesurvey.co.uk/business-and-government/products/os-net/index.html):
### Accuracy of *Your* Data
Conversion of your coordinates using OSTN15 transformations will be accurate, but if you're using consumer equipment, or got your data off the web, be aware that you're converting coordinates which probably weren't accurately recorded in the first place. That's because [accurate surveying is difficult](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html).
### Accuracy of the OSTN15 transformation used in this library
- ETRS89 longitude and latitude / Eastings and Northings to OSGB36 conversion agrees with the provided Ordnance Survey test data in **39 of the 40** test coordinates (excluding two coordinates designed to return no data; these correctly fail).
- The only discrepancy – in point `TP31`– is **1mm**.
- OSGB36 to ETRS89 longitude and latitude conversion is accurate to within 8 decimal places, or 1.1mm.
### A Note on Ellipsoids
WGS84 and ETRS89 coordinates use the GRS80 ellipsoid, whereas OSGB36 uses the Airy 1830 ellipsoid, which provides a regional best fit for Britain. Positions for coordinates in Great Britain can differ by over 100m as a result. It is thus inadvisable to attempt calculations using mixed ETRS89 and OSGB36 coordinates.
[]( "OSTN15")
## Implementation
The main detail of interest is the FFI interface between Python and Rust, the Python side of which can be found in [util.py](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L64-L100) (the `ctypes` implementation), [cutil.pyx](https://github.com/urschrei/convertbng/blob/master/convertbng/cutil.pyx#L51-L86) (the `cython` implementation), and the Rust side of which can be found in [ffi.rs](https://github.com/urschrei/rust_bng/blob/master/src/ffi.rs#L47-L271).
The [ctypes](https://docs.python.org/2/library/ctypes.html) library expects C-compatible data structures, which we define in Rust (see above). We then define methods which allow us to receive, safely access, return, and free data across the FFI boundary.
Finally, we link the Rust conversion functions from `util.py` [again](https://github.com/urschrei/convertbng/blob/master/convertbng/util.py#L103-L205). Note the `errcheck` assignments, which convert the FFI-compatible ctypes data structures to tuple lists.
# Building the binary for local development
- ensure you have Rust 1.x and Cargo [installed](https://www.rustup.rs)
- clone https://github.com/urschrei/lonlat_bng, and ensure it's adjacent to this dir (i.e. `code/witnessme/convertbng` and `code/witnessme/rust_bng`)
- in this dir, run `make clean` then `make`
# Tests
You can run the Python module tests by running "make test".
Tests require both `numpy` and `nose`.
# License
[MIT](license.txt)
## Citing `Convertbng`
If Convertbng has been significant in your research, and you would like to acknowledge the project in your academic publication, we suggest citing it as follows (example in APA style, 7th edition):
> Hügel, S. (2021). Convertbng (Version X.Y.Z) [Computer software]. https://doi.org/10.5281/zenodo.5774931
In Bibtex format:
@software{Hugel_Convertbng_2021,
author = {Hügel, Stephan},
doi = {10.5281/zenodo.5774931},
license = {MIT},
month = {12},
title = {{Convertbng}},
url = {https://github.com/urschrei/convertbng},
version = {X.Y.Z},
year = {2021}
}
%prep
%autosetup -n convertbng-0.6.41
%build
%py3_build
%install
%py3_install
install -d -m755 %{buildroot}/%{_pkgdocdir}
if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
pushd %{buildroot}
if [ -d usr/lib ]; then
find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/lib64 ]; then
find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/bin ]; then
find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
fi
if [ -d usr/sbin ]; then
find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
fi
touch doclist.lst
if [ -d usr/share/man ]; then
find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
fi
popd
mv %{buildroot}/filelist.lst .
mv %{buildroot}/doclist.lst .
%files -n python3-convertbng -f filelist.lst
%dir %{python3_sitearch}/*
%files help -f doclist.lst
%{_docdir}/*
%changelog
* Tue Apr 25 2023 Python_Bot <Python_Bot@openeuler.org> - 0.6.41-1
- Package Spec generated
|