Skip to content

Commit f13e7f0

Browse files
authored
Issue3 (#15)
* changed to match official API * added example
1 parent 462783c commit f13e7f0

File tree

4 files changed

+224
-169
lines changed

4 files changed

+224
-169
lines changed

Convert.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package proj
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-spatial/proj/core"
7+
"github.com/go-spatial/proj/support"
8+
9+
// need to pull in the operations table entries
10+
_ "github.com/go-spatial/proj/operations"
11+
)
12+
13+
// EPSGCode is the enum type for coordinate systems
14+
type EPSGCode int
15+
16+
// Supported EPSG codes
17+
const (
18+
EPSG3395 EPSGCode = 3395
19+
WorldMercator = EPSG3395
20+
EPSG3857 = 3857
21+
WebMercator = EPSG3857
22+
)
23+
24+
// Convert performs a conversion from a 4326 coordinate system (lon/lat
25+
// degrees, 2D) to the given projected system (x/y meters, 2D).
26+
//
27+
// The input is assumed to be an array of lon/lat points, e.g. [lon0, lat0,
28+
// lon1, lat1, lon2, lat2, ...]. The length of the array must, therefore, be
29+
// even.
30+
//
31+
// The returned output is a similar array of x/y points, e.g. [x0, y0, x1,
32+
// y1, x2, y2, ...].
33+
func Convert(dest EPSGCode, input []float64) ([]float64, error) {
34+
35+
conv, err := newConversion(dest)
36+
if err != nil {
37+
return nil, nil
38+
}
39+
40+
return conv.convert(input)
41+
}
42+
43+
//---------------------------------------------------------------------------
44+
45+
// conversion holds the objects needed to perform a conversion
46+
type conversion struct {
47+
dest EPSGCode
48+
projString *support.ProjString
49+
system *core.System
50+
operation core.IOperation
51+
converter core.IConvertLPToXY
52+
}
53+
54+
var conversions = map[EPSGCode]*conversion{}
55+
56+
var projStrings = map[EPSGCode]string{
57+
EPSG3395: "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84", // TODO: support +units=m +no_defs
58+
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
59+
}
60+
61+
// newConversion creates a conversion object for the destination systems. If
62+
// such a conversion already exists in the cache, use that.
63+
func newConversion(dest EPSGCode) (*conversion, error) {
64+
65+
str, ok := projStrings[dest]
66+
if !ok {
67+
return nil, fmt.Errorf("epsg code is not a supported projection")
68+
}
69+
70+
conv, ok := conversions[dest]
71+
if ok {
72+
return conv, nil
73+
}
74+
75+
// need to build it
76+
77+
ps, err := support.NewProjString(str)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
sys, opx, err := core.NewSystem(ps)
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
if !opx.GetDescription().IsConvertLPToXY() {
88+
return nil, fmt.Errorf("projection type is not supported")
89+
}
90+
91+
conv = &conversion{
92+
dest: dest,
93+
projString: ps,
94+
system: sys,
95+
operation: opx,
96+
converter: opx.(core.IConvertLPToXY),
97+
}
98+
99+
// cache it
100+
conversions[dest] = conv
101+
102+
return conv, nil
103+
}
104+
105+
// convert performs the projection on the given input points
106+
func (conv *conversion) convert(input []float64) ([]float64, error) {
107+
108+
if conv == nil || conv.converter == nil {
109+
return nil, fmt.Errorf("conversion not initialized")
110+
}
111+
112+
if len(input)%2 != 0 {
113+
return nil, fmt.Errorf("input array of lon/lat values must be an even number")
114+
}
115+
116+
output := make([]float64, len(input))
117+
118+
lp := &core.CoordLP{}
119+
120+
for i := 0; i < len(input); i += 2 {
121+
lp.Lam = support.DDToR(input[i])
122+
lp.Phi = support.DDToR(input[i+1])
123+
124+
xy, err := conv.converter.Forward(lp)
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
output[i] = xy.X
130+
output[i+1] = xy.Y
131+
}
132+
133+
return output, nil
134+
}

Convert_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package proj_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/go-spatial/proj"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
var inputA = []float64{
12+
-0.127758, 51.507351, // London
13+
2.352222, 48.856614, // Paris
14+
12.496366, 41.902783, // Rome
15+
}
16+
var inputB = []float64{
17+
-77.625583, 38.833846, // mpg
18+
}
19+
20+
type testcase struct {
21+
dest proj.EPSGCode
22+
expectedA []float64
23+
expectedB []float64
24+
}
25+
26+
var testcases = []testcase{
27+
{
28+
dest: proj.EPSG3395,
29+
expectedA: []float64{
30+
-14221.96, 6678068.96,
31+
261848.16, 6218371.80,
32+
1391089.10, 5117883.04,
33+
},
34+
expectedB: []float64{
35+
-8641240.37, 4671101.60,
36+
},
37+
},
38+
{
39+
dest: proj.EPSG3857,
40+
expectedA: []float64{
41+
-14221.96, 6711533.71,
42+
261848.16, 6250566.72,
43+
1391089.10, 5146427.91,
44+
},
45+
expectedB: []float64{
46+
-8641240.37, 4697899.31,
47+
},
48+
},
49+
}
50+
51+
func TestConvert(t *testing.T) {
52+
assert := assert.New(t)
53+
54+
for _, tc := range testcases {
55+
56+
outputA, err := proj.Convert(tc.dest, inputA)
57+
assert.NoError(err)
58+
59+
outputB, err := proj.Convert(tc.dest, inputB)
60+
assert.NoError(err)
61+
62+
const tol = 1.0e-2
63+
64+
for i := range tc.expectedA {
65+
tag := fmt.Sprintf("epsg:%d, input=A.%d", int(tc.dest), i)
66+
assert.InDelta(tc.expectedA[i], outputA[i], tol, tag)
67+
assert.InDelta(tc.expectedA[i], outputA[i], tol, tag)
68+
}
69+
for i := range tc.expectedB {
70+
tag := fmt.Sprintf("epsg:%d, input=B.%d", int(tc.dest), i)
71+
assert.InDelta(tc.expectedB[i], outputB[i], tol, tag)
72+
assert.InDelta(tc.expectedB[i], outputB[i], tol, tag)
73+
}
74+
}
75+
}
76+
77+
func ExampleConvert() {
78+
79+
var dd = []float64{
80+
-77.625583, 38.833846,
81+
}
82+
83+
xy, err := proj.Convert(proj.EPSG3395, dd)
84+
if err != nil {
85+
panic(err)
86+
}
87+
88+
fmt.Printf("%.2f, %.2f\n", xy[0], xy[1])
89+
// Output: -8641240.37, 4671101.60
90+
}

Proj.go

Lines changed: 0 additions & 105 deletions
This file was deleted.

Proj_test.go

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)