-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathraster.go
274 lines (234 loc) · 6.74 KB
/
raster.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
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
// Copyright 2015 Simon HEGE. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
/*
Package raster provides raster/tiles manipulation helpers.
Conversion between latitude/longitude and x/y in global-mercator is performed as described
in http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
*/
package raster
import (
"bytes"
"errors"
"image"
"image/jpeg"
"image/png"
"math"
"github.com/xeonx/geographic"
)
//n returns 2^level
func n(level int) int {
return 1 << uint(level)
}
//X2Lon transforms x into a longitude in degree at a given level.
func X2Lon(level int, x int) float64 {
return float64(x)/float64(n(level))*360.0 - 180.0
}
//Lon2X transforms a longitude in degree into x at a given level.
func Lon2X(level int, longitudeDeg float64) int {
return int(float64(n(level)) * (longitudeDeg + 180.) / 360.)
}
//Y2Lat transforms y into a latitude in degree at a given level.
func Y2Lat(level int, y int) float64 {
var yosm = y
latitudeRad := math.Atan(math.Sinh(math.Pi * (1. - 2.*float64(yosm)/float64(n(level)))))
return -(latitudeRad * 180.0 / math.Pi)
}
//Lat2Y transforms a latitude in degree into y at a given level.
func Lat2Y(level int, latitudeDeg float64) int {
latitudeRad := latitudeDeg / 180 * math.Pi
yosm := int(float64(n(level)) * (1. - math.Log(math.Tan(latitudeRad)+1/math.Cos(latitudeRad))/math.Pi) / 2.)
return n(level) - yosm - 1
}
//Encode encodes an image in the given format. Only jpg and png are supported.
func Encode(img image.Image, format string) ([]byte, error) {
var b bytes.Buffer
if format == "jpg" {
err := jpeg.Encode(&b, img, nil)
if err != nil {
return nil, err
}
} else if format == "png" {
err := png.Encode(&b, img)
if err != nil {
return nil, err
}
} else {
return nil, errors.New("Unsupported image format: '" + format + "'. Only 'jpg' or 'png' allowed.")
}
return b.Bytes(), nil
}
//Decode decodes an image into the given format. Only jpg and png are supported.
func Decode(rawImg []byte, format string) (image.Image, error) {
b := bytes.NewBuffer(rawImg)
var img image.Image
var err error
if format == "jpg" {
img, err = jpeg.Decode(b)
if err != nil {
return nil, err
}
} else if format == "png" {
img, err = png.Decode(b)
if err != nil {
return nil, err
}
} else {
return nil, errors.New("Unsupported image format: '" + format + "'. Only 'jpg' or 'png' allowed.")
}
return img, nil
}
//Filter returns true if the given tile is excluded from copy
type Filter func(level, x, y int) (bool, error)
//Any composes Filter in a single Filter excluding a tile if at least one Filter excludes the tile.
func Any(filters ...Filter) Filter {
return func(level, x, y int) (bool, error) {
for _, f := range filters {
b, err := f(level, x, y)
if err != nil {
return false, err
}
if b {
return true, nil
}
}
return false, nil
}
}
//All composes Filter in a single Filter excluding a tile if all Filters excludes the tile.
func All(filters ...Filter) Filter {
return func(level, x, y int) (bool, error) {
for _, f := range filters {
b, err := f(level, x, y)
if err != nil {
return false, err
}
if !b {
return false, nil
}
}
return true, nil
}
}
//transformFct is any transformation function usable inside the encoding or decoding of images.
type transformFct func(src []byte) ([]byte, error)
//GetTansformFct returns a function able to convert between the raw format of from and the one of to.
//A nil value means that no transformation is required.
func getTansformFct(from TileReader, to TileReadWriter) transformFct {
var transform transformFct
if from.TileFormat() != to.TileFormat() {
transform = func(src []byte) ([]byte, error) {
img, err := Decode(src, from.TileFormat())
if err != nil {
return nil, err
}
return Encode(img, to.TileFormat())
}
}
return transform
}
//Copier copies tiles from a TileReader to a TileReadWriter.
//An optional filter allow to discard Tiles before copy.
type Copier struct {
from TileReader
to TileReadWriter
transform transformFct
Filter Filter
}
//NewCopier creates a Copier between from and to.
func NewCopier(from TileReader, to TileReadWriter) (*Copier, error) {
return &Copier{
from: from,
to: to,
transform: getTansformFct(from, to),
}, nil
}
//TileBlock represents a rectangular set of tiles at a given level
type TileBlock struct {
Level int
Xmin int
Xmax int
Ymin int
Ymax int
}
//Count returns the number of tiles within the block.
func (b TileBlock) Count() int {
return (b.Xmax - b.Xmin + 1) * (b.Ymax - b.Ymin + 1)
}
//GetTileBlock computes the tile block enveloping the bounding box
func GetTileBlock(bbox geographic.BoundingBox, level int) (TileBlock, error) {
b := TileBlock{
Level: level,
Xmin: Lon2X(level, bbox.LongitudeMinDeg),
Xmax: Lon2X(level, bbox.LongitudeMaxDeg),
Ymin: Lat2Y(level, bbox.LatitudeMinDeg),
Ymax: Lat2Y(level, bbox.LatitudeMaxDeg),
}
if b.Ymin > b.Ymax {
y := b.Ymin
b.Ymin = b.Ymax
b.Ymax = y
}
if X2Lon(level, b.Xmax) == bbox.LongitudeMaxDeg {
b.Xmax--
}
return b, nil
}
//CopyBlock copies a block of tiles.
//If progressFct is not nil, it is called during the iteration after each tile.
//It returns the count of tiles copied in the destination and the first error encountered, if any.
func (c *Copier) CopyBlock(block TileBlock, progressFct func(level, x, y int, processed bool)) (int, error) {
processedCount := 0
for x := block.Xmin; x <= block.Xmax; x++ {
for y := block.Ymin; y <= block.Ymax; y++ {
processed, err := c.Copy(block.Level, x, y)
if err != nil {
return processedCount, err
}
if progressFct != nil {
progressFct(block.Level, x, y, processed)
}
if processed {
processedCount++
}
}
}
return processedCount, nil
}
//Copy copies a single of tile.
//It returns the true if the tile was copied in the destination and the first error encountered, if any.
func (c *Copier) Copy(level, x, y int) (bool, error) {
if c.Filter != nil {
filtered, err := c.Filter(level, x, y)
if err != nil {
return false, err
}
if filtered {
return false, nil
}
}
rawImg, err := c.from.GetRaw(level, x, y)
if err != nil {
return false, err
}
if c.transform != nil {
rawImg, err = c.transform(rawImg)
if err != nil {
return false, err
}
}
err = c.to.SetRaw(level, x, y, rawImg)
if err != nil {
return false, err
}
return true, nil
}
//Copy copies a single tile from a reader to a writer.
//It returns the true if the tile was copied in the destination and the first error encountered, if any.
func Copy(from TileReader, to TileReadWriter, level, x, y int) (bool, error) {
c, err := NewCopier(from, to)
if err != nil {
return false, err
}
return c.Copy(level, x, y)
}