forked from go-spatial/proj
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathConvert.go
204 lines (159 loc) · 5.12 KB
/
Convert.go
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
// Copyright (C) 2018, Michael P. Gerlek (Flaxen Consulting)
//
// Portions of this code were derived from the PROJ.4 software
// In keeping with the terms of the PROJ.4 project, this software
// is provided under the MIT-style license in `LICENSE.md` and may
// additionally be subject to the copyrights of the PROJ.4 authors.
package proj
import (
"fmt"
"sync"
"github.com/go-spatial/proj/core"
"github.com/go-spatial/proj/support"
// need to pull in the operations table entries
_ "github.com/go-spatial/proj/operations"
)
// EPSGCode is the enum type for coordinate systems
type EPSGCode int
// Supported EPSG codes
const (
EPSG3395 EPSGCode = 3395
WorldMercator = EPSG3395
EPSG3857 = 3857
WebMercator = EPSG3857
EPSG4087 = 4087
WorldEquidistantCylindrical = EPSG4087
EPSG4326 = 4326
WGS84 = EPSG4326
)
// ensure only one person is updating our cache of converters at a time
var cacheLock = sync.Mutex{}
// Convert performs a conversion from a 4326 coordinate system (lon/lat
// degrees, 2D) to the given projected system (x/y meters, 2D).
//
// The input is assumed to be an array of lon/lat points, e.g. [lon0, lat0,
// lon1, lat1, lon2, lat2, ...]. The length of the array must, therefore, be
// even.
//
// The returned output is a similar array of x/y points, e.g. [x0, y0, x1,
// y1, x2, y2, ...].
func Convert(dest EPSGCode, input []float64) ([]float64, error) {
cacheLock.Lock()
conv, err := newConversion(dest)
cacheLock.Unlock()
if err != nil {
return nil, err
}
return conv.convert(input)
}
// Inverse converts from a projected X/Y of a coordinate system to
// 4326 (lat/lon, 2D).
//
// The input is assumed to be an array of x/y points, e.g. [x0, y0,
// x1, y1, x2, y2, ...]. The length of the array must, therefore, be
// even.
//
// The returned output is a similar array of lon/lat points, e.g. [lon0, lat0, lon1,
// lat1, lon2, lat2, ...].
func Inverse(src EPSGCode, input []float64) ([]float64, error) {
cacheLock.Lock()
conv, err := newConversion(src)
cacheLock.Unlock()
if err != nil {
return nil, err
}
return conv.inverse(input)
}
//---------------------------------------------------------------------------
// conversion holds the objects needed to perform a conversion
type conversion struct {
dest EPSGCode
projString *support.ProjString
system *core.System
operation core.IOperation
converter core.IConvertLPToXY
}
var conversions = map[EPSGCode]*conversion{}
var projStrings = map[EPSGCode]string{
EPSG3395: "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84", // TODO: support +units=m +no_defs
EPSG3857: "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0", // TODO: support +units=m +nadgrids=@null +wktext +no_defs
EPSG4087: "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84", // TODO: support +units=m +no_defs
}
// newConversion creates a conversion object for the destination systems. If
// such a conversion already exists in the cache, use that.
func newConversion(dest EPSGCode) (*conversion, error) {
str, ok := projStrings[dest]
if !ok {
return nil, fmt.Errorf("epsg code is not a supported projection")
}
conv, ok := conversions[dest]
if ok {
return conv, nil
}
// need to build it
ps, err := support.NewProjString(str)
if err != nil {
return nil, err
}
sys, opx, err := core.NewSystem(ps)
if err != nil {
return nil, err
}
if !opx.GetDescription().IsConvertLPToXY() {
return nil, fmt.Errorf("projection type is not supported")
}
conv = &conversion{
dest: dest,
projString: ps,
system: sys,
operation: opx,
converter: opx.(core.IConvertLPToXY),
}
// cache it
conversions[dest] = conv
return conv, nil
}
// convert performs the projection on the given input points
func (conv *conversion) convert(input []float64) ([]float64, error) {
if conv == nil || conv.converter == nil {
return nil, fmt.Errorf("conversion not initialized")
}
if len(input)%2 != 0 {
return nil, fmt.Errorf("input array of lon/lat values must be an even number")
}
output := make([]float64, len(input))
lp := &core.CoordLP{}
for i := 0; i < len(input); i += 2 {
lp.Lam = support.DDToR(input[i])
lp.Phi = support.DDToR(input[i+1])
xy, err := conv.converter.Forward(lp)
if err != nil {
return nil, err
}
output[i] = xy.X
output[i+1] = xy.Y
}
return output, nil
}
func (conv *conversion) inverse(input []float64) ([]float64, error) {
if conv == nil || conv.converter == nil {
return nil, fmt.Errorf("conversion not initialized")
}
if len(input)%2 != 0 {
return nil, fmt.Errorf("input array of x/y values must be an even number")
}
output := make([]float64, len(input))
xy := &core.CoordXY{}
for i := 0; i < len(input); i += 2 {
xy.X = input[i]
xy.Y = input[i+1]
lp, err := conv.converter.Inverse(xy)
if err != nil {
return nil, err
}
l, p := lp.Lam, lp.Phi
output[i] = support.RToDD(l)
output[i+1] = support.RToDD(p)
}
return output, nil
}