imposm3/expire/tilelist.go

243 lines
4.9 KiB
Go

package expire
import (
"fmt"
"io"
"math"
"os"
"path/filepath"
"sync"
"time"
"github.com/omniscale/imposm3/element"
"github.com/omniscale/imposm3/proj"
)
var mercBbox = [4]float64{
-20037508.342789244,
-20037508.342789244,
20037508.342789244,
20037508.342789244,
}
var mercRes [20]float64
func init() {
res := 2 * 20037508.342789244 / 256
for i, _ := range mercRes {
mercRes[i] = res
res /= 2
}
}
func tileCoord(long, lat float64, zoom int) (float64, float64) {
x, y := proj.WgsToMerc(long, lat)
res := mercRes[zoom]
x = x - mercBbox[0]
y = mercBbox[3] - y
tileX := float64(x / (res * 256))
tileY := float64(y / (res * 256))
return tileX, tileY
}
type TileList struct {
mu sync.Mutex
tiles map[tileKey]struct{}
zoom int
out string
}
type tileKey struct {
X uint32
Y uint32
}
func NewTileList(zoom int, out string) *TileList {
return &TileList{
tiles: make(map[tileKey]struct{}),
zoom: zoom,
mu: sync.Mutex{},
out: out,
}
}
func (tl *TileList) Expire(long, lat float64) {
tl.addCoord(long, lat)
}
func (tl *TileList) ExpireNodes(nodes []element.Node, closed bool) {
if len(nodes) == 0 {
return
}
if closed {
box := nodesBbox(nodes)
tiles := numBboxTiles(box, tl.zoom)
if tiles > 500 {
tl.expireLine(nodes)
} else {
tl.expireBox(box)
}
} else {
tl.expireLine(nodes)
}
}
// expire a single point. Point is padded by 0.2 tiles to expire nearby tiles
// for nodes at a tile border.
func (tl *TileList) addCoord(long, lat float64) {
// fraction of a tile that is added as a padding around a single node
const tilePadding = 0.2
tl.mu.Lock()
tileX, tileY := tileCoord(long, lat, tl.zoom)
for x := uint32(tileX - tilePadding); x <= uint32(tileX+tilePadding); x++ {
for y := uint32(tileY - tilePadding); y <= uint32(tileY+tilePadding); y++ {
tl.tiles[tileKey{x, y}] = struct{}{}
}
}
tl.mu.Unlock()
}
// expireLine expires all tiles that are intersected by the line segments
// between the nodes
func (tl *TileList) expireLine(nodes []element.Node) {
if len(nodes) == 1 {
tl.addCoord(nodes[0].Long, nodes[0].Lat)
return
}
tl.mu.Lock()
defer tl.mu.Unlock()
for i := 0; i < len(nodes)-1; i++ {
x1, y1 := tileCoord(nodes[i].Long, nodes[i].Lat, tl.zoom)
x2, y2 := tileCoord(nodes[i+1].Long, nodes[i+1].Lat, tl.zoom)
if int(x1) == int(x2) && int(y1) == int(y2) {
tl.tiles[tileKey{X: uint32(x1), Y: uint32(y1)}] = struct{}{}
} else {
for _, tk := range bresenham(x1, y1, x2, y2) {
tl.tiles[tk] = struct{}{}
}
}
}
}
// expireBox expires all tiles inside the bbox
func (tl *TileList) expireBox(b bbox) {
tl.mu.Lock()
defer tl.mu.Unlock()
x1, y1 := tileCoord(b.minx, b.maxy, tl.zoom)
x2, y2 := tileCoord(b.maxx, b.miny, tl.zoom)
for x := uint32(x1); x <= uint32(x2); x++ {
for y := uint32(y1); y <= uint32(y2); y++ {
tl.tiles[tileKey{x, y}] = struct{}{}
}
}
}
func (tl *TileList) writeTiles(w io.Writer) error {
for tileKey, _ := range tl.tiles {
_, err := fmt.Fprintf(w, "%d/%d/%d\n", tl.zoom, tileKey.X, tileKey.Y)
if err != nil {
return err
}
}
return nil
}
func (tl *TileList) Flush() error {
tl.mu.Lock()
defer tl.mu.Unlock()
if len(tl.tiles) == 0 {
return nil
}
now := time.Now().UTC()
dir := filepath.Join(tl.out, now.Format("20060102"))
err := os.MkdirAll(dir, 0775)
if err != nil {
return err
}
fileName := filepath.Join(dir, now.Format("150405.000")+".tiles~")
f, err := os.Create(fileName)
if err != nil {
return err
}
err = tl.writeTiles(f)
f.Close()
if err != nil {
return err
}
tl.tiles = make(map[tileKey]struct{})
// wrote to .tiles~ and now atomically move file to .tiles
return os.Rename(fileName, fileName[0:len(fileName)-1])
}
type bbox struct {
minx, miny, maxx, maxy float64
}
func nodesBbox(nodes []element.Node) bbox {
b := bbox{nodes[0].Long, nodes[0].Lat, nodes[0].Long, nodes[0].Lat}
for i := 1; i < len(nodes); i++ {
if b.maxx < nodes[i].Long {
b.maxx = nodes[i].Long
}
if b.maxy < nodes[i].Lat {
b.maxy = nodes[i].Lat
}
if b.minx > nodes[i].Long {
b.minx = nodes[i].Long
}
if b.miny > nodes[i].Lat {
b.miny = nodes[i].Lat
}
}
return b
}
func numBboxTiles(b bbox, zoom int) int {
x1, y1 := tileCoord(b.minx, b.maxy, zoom)
x2, y2 := tileCoord(b.maxx, b.miny, zoom)
return int(math.Abs((x2 - x1 + 1) * (y2 - y1 + 1)))
}
func bresenham(x1, y1, x2, y2 float64) []tileKey {
tiles := make([]tileKey, 0, 4)
steep := false
dx := math.Abs(x2 - x1)
sx := -1.0
if (x2 - x1) > 0 {
sx = 1.0
}
dy := math.Abs(y2 - y1)
sy := -1.0
if (y2 - y1) > 0 {
sy = 1.0
}
if dy > dx {
steep = true
x1, y1 = y1, x1
dx, dy = dy, dx
sx, sy = sy, sx
}
e := 2*dy - dx
for i := 0.0; i < dx; i++ {
if steep {
tiles = append(tiles, tileKey{X: uint32(y1), Y: uint32(x1)})
} else {
tiles = append(tiles, tileKey{X: uint32(x1), Y: uint32(y1)})
}
for e >= 0 {
y1 += sy
e -= 2 * dx
}
x1 += sx
e += 2 * dy
}
tiles = append(tiles, tileKey{X: uint32(x2), Y: uint32(y2)})
return tiles
}