Skip to content

Commit 7d903f0

Browse files
authored
Merge pull request #32 from ProminentEdge/add-4087
Add in Inverse convenience method + 4087 proj support
2 parents f0dac9b + 343fc84 commit 7d903f0

File tree

2 files changed

+138
-6
lines changed

2 files changed

+138
-6
lines changed

Convert.go

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ type EPSGCode int
2323

2424
// Supported EPSG codes
2525
const (
26-
EPSG3395 EPSGCode = 3395
27-
WorldMercator = EPSG3395
28-
EPSG3857 = 3857
29-
WebMercator = EPSG3857
30-
EPSG4087 = 4087
26+
EPSG3395 EPSGCode = 3395
27+
WorldMercator = EPSG3395
28+
EPSG3857 = 3857
29+
WebMercator = EPSG3857
30+
EPSG4087 = 4087
31+
WorldEquidistantCylindrical = EPSG4087
32+
EPSG4326 = 4326
33+
WGS84 = EPSG4326
3134
)
3235

3336
// ensure only one person is updating our cache of converters at a time
@@ -54,6 +57,26 @@ func Convert(dest EPSGCode, input []float64) ([]float64, error) {
5457
return conv.convert(input)
5558
}
5659

60+
// Inverse converts from a projected X/Y of a coordinate system to
61+
// 4326 (lat/lon, 2D).
62+
//
63+
// The input is assumed to be an array of x/y points, e.g. [x0, y0,
64+
// x1, y1, x2, y2, ...]. The length of the array must, therefore, be
65+
// even.
66+
//
67+
// The returned output is a similar array of lon/lat points, e.g. [lon0, lat0, lon1,
68+
// lat1, lon2, lat2, ...].
69+
func Inverse(src EPSGCode, input []float64) ([]float64, error) {
70+
cacheLock.Lock()
71+
conv, err := newConversion(src)
72+
cacheLock.Unlock()
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
return conv.inverse(input)
78+
}
79+
5780
//---------------------------------------------------------------------------
5881

5982
// conversion holds the objects needed to perform a conversion
@@ -70,7 +93,7 @@ var conversions = map[EPSGCode]*conversion{}
7093
var projStrings = map[EPSGCode]string{
7194
EPSG3395: "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84", // TODO: support +units=m +no_defs
7295
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
73-
EPSG4087: "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84",
96+
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
7497
}
7598

7699
// newConversion creates a conversion object for the destination systems. If
@@ -147,3 +170,35 @@ func (conv *conversion) convert(input []float64) ([]float64, error) {
147170

148171
return output, nil
149172
}
173+
174+
func (conv *conversion) inverse(input []float64) ([]float64, error) {
175+
if conv == nil || conv.converter == nil {
176+
return nil, fmt.Errorf("conversion not initialized")
177+
}
178+
179+
if len(input)%2 != 0 {
180+
return nil, fmt.Errorf("input array of x/y values must be an even number")
181+
}
182+
183+
output := make([]float64, len(input))
184+
185+
xy := &core.CoordXY{}
186+
187+
for i := 0; i < len(input); i += 2 {
188+
xy.X = input[i]
189+
xy.Y = input[i+1]
190+
191+
lp, err := conv.converter.Inverse(xy)
192+
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
l, p := lp.Lam, lp.Phi
198+
199+
output[i] = support.RToDD(l)
200+
output[i+1] = support.RToDD(p)
201+
}
202+
203+
return output, nil
204+
}

Convert_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ func TestConvert(t *testing.T) {
7777
outputB, err := proj.Convert(tc.dest, inputB)
7878
assert.NoError(err)
7979

80+
invA, err := proj.Inverse(tc.dest, tc.expectedA)
81+
assert.NoError(err)
82+
83+
invB, err := proj.Inverse(tc.dest, tc.expectedB)
84+
assert.NoError(err)
85+
8086
const tol = 1.0e-2
8187

8288
for i := range tc.expectedA {
@@ -89,6 +95,77 @@ func TestConvert(t *testing.T) {
8995
assert.InDelta(tc.expectedB[i], outputB[i], tol, tag)
9096
assert.InDelta(tc.expectedB[i], outputB[i], tol, tag)
9197
}
98+
99+
for i := range tc.expectedA {
100+
tag := fmt.Sprintf("inverse: epsg:%d, input=A.%d", int(tc.dest), i)
101+
assert.InDelta(invA[i], inputA[i], tol, tag)
102+
}
103+
104+
for i := range tc.expectedB {
105+
tag := fmt.Sprintf("inverse: epsg:%d, input=B.%d", int(tc.dest), i)
106+
assert.InDelta(invB[i], inputB[i], tol, tag)
107+
}
108+
}
109+
}
110+
111+
func TestEnsureRaisedError(t *testing.T) {
112+
type testcase struct {
113+
op string
114+
pt []float64
115+
expectedErr string
116+
srid proj.EPSGCode
117+
}
118+
119+
fn := func(tc testcase) func(t *testing.T) {
120+
return func(t *testing.T) {
121+
var err error
122+
123+
if tc.op == "convert" {
124+
_, err = proj.Convert(proj.EPSGCode(tc.srid), tc.pt)
125+
} else {
126+
_, err = proj.Inverse(proj.EPSGCode(tc.srid), tc.pt)
127+
}
128+
129+
if err == nil {
130+
t.Errorf("didn't get expected error: %v", tc.expectedErr)
131+
return
132+
}
133+
134+
if err.Error() != tc.expectedErr {
135+
t.Errorf("error: %v not equal to expected error: %v", err.Error(), tc.expectedErr)
136+
}
137+
}
138+
}
139+
140+
tests := map[string]testcase{
141+
"3857 out of bounds WGS84": {
142+
op: "convert",
143+
srid: proj.WebMercator,
144+
pt: []float64{-180.0, 90.0},
145+
expectedErr: "tolerance condition error",
146+
},
147+
"4326 not supported as source srid": {
148+
op: "convert",
149+
srid: proj.EPSG4326,
150+
pt: []float64{0, 0},
151+
expectedErr: "epsg code is not a supported projection",
152+
},
153+
"convert bad point count": {
154+
op: "convert",
155+
srid: proj.WorldMercator,
156+
pt: []float64{-180.0, 90.0, 11.0},
157+
expectedErr: "input array of lon/lat values must be an even number",
158+
},
159+
"inverse bad point count": {
160+
op: "inverse",
161+
srid: proj.WorldMercator,
162+
pt: []float64{-180.0, 90.0, 11.0},
163+
expectedErr: "input array of x/y values must be an even number",
164+
},
165+
}
166+
167+
for name, tc := range tests {
168+
t.Run(name, fn(tc))
92169
}
93170
}
94171

0 commit comments

Comments
 (0)