expire polygon areas and linestrings
parent
ea973f769b
commit
3fd7719104
|
@ -3,6 +3,7 @@ package expire
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
@ -30,31 +31,21 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// fraction of a tile that is added as a padding around an expired tile
|
||||
const tilePadding = 0.1
|
||||
|
||||
func TileCoords(long, lat float64, zoom uint32) []tileKey {
|
||||
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 := float32(x / (res * 256))
|
||||
tileY := float32(y / (res * 256))
|
||||
|
||||
tiles := make([]tileKey, 0, 4)
|
||||
for x := uint32(tileX - tilePadding); x <= uint32(tileX+tilePadding); x++ {
|
||||
for y := uint32(tileY - tilePadding); y <= uint32(tileY+tilePadding); y++ {
|
||||
tiles = append(tiles, tileKey{x, y})
|
||||
}
|
||||
}
|
||||
return tiles
|
||||
tileX := float64(x / (res * 256))
|
||||
tileY := float64(y / (res * 256))
|
||||
return tileX, tileY
|
||||
}
|
||||
|
||||
type TileList struct {
|
||||
mu sync.Mutex
|
||||
tiles map[tileKey]struct{}
|
||||
|
||||
zoom uint32
|
||||
zoom int
|
||||
out string
|
||||
}
|
||||
|
||||
|
@ -72,27 +63,80 @@ type tile struct {
|
|||
func NewTileList(zoom int, out string) *TileList {
|
||||
return &TileList{
|
||||
tiles: make(map[tileKey]struct{}),
|
||||
zoom: uint32(zoom),
|
||||
zoom: zoom,
|
||||
mu: sync.Mutex{},
|
||||
out: out,
|
||||
}
|
||||
}
|
||||
|
||||
func (tl *TileList) addCoord(long, lat float64) {
|
||||
tl.mu.Lock()
|
||||
for _, t := range TileCoords(long, lat, tl.zoom) {
|
||||
tl.tiles[t] = struct{}{}
|
||||
}
|
||||
tl.mu.Unlock()
|
||||
}
|
||||
|
||||
func (tl *TileList) Expire(long, lat float64) {
|
||||
tl.addCoord(long, lat)
|
||||
}
|
||||
|
||||
func (tl *TileList) ExpireNodes(nodes []element.Node, closed bool) {
|
||||
for _, nd := range nodes {
|
||||
tl.addCoord(nd.Long, nd.Lat)
|
||||
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{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,3 +174,72 @@ func (tl *TileList) Flush() error {
|
|||
// 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
|
||||
}
|
||||
|
|
|
@ -2,31 +2,81 @@ package expire
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/omniscale/imposm3/element"
|
||||
)
|
||||
|
||||
func TestTileCoords(t *testing.T) {
|
||||
func TestTileList_ExpireNodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
long float64
|
||||
lat float64
|
||||
zoom uint32
|
||||
tiles []tileKey
|
||||
nodes []element.Node
|
||||
expected int
|
||||
polygon bool
|
||||
}{
|
||||
{0, 0, 14, []tileKey{{8191, 8191}, {8191, 8192}, {8192, 8191}, {8192, 8192}}},
|
||||
{0.01, 0, 14, []tileKey{{8192, 8191}, {8192, 8192}}},
|
||||
{0, 0.01, 14, []tileKey{{8191, 8191}, {8192, 8191}}},
|
||||
{0.01, 0.01, 14, []tileKey{{8192, 8191}}},
|
||||
{0.02, 0.01, 14, []tileKey{{8192, 8191}, {8193, 8191}}},
|
||||
}
|
||||
// point
|
||||
{[]element.Node{{Long: 8.30, Lat: 53.26}}, 1, false},
|
||||
|
||||
// point + paddings
|
||||
{[]element.Node{{Long: 0, Lat: 0}}, 4, false},
|
||||
{[]element.Node{{Long: 0.01, Lat: 0}}, 2, false},
|
||||
{[]element.Node{{Long: 0, Lat: 0.01}}, 2, false},
|
||||
{[]element.Node{{Long: 0.01, Lat: 0.01}}, 1, false},
|
||||
|
||||
// line
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.30},
|
||||
}, 5, false},
|
||||
// same line, but split into multiple segments
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.27},
|
||||
{Long: 8.30, Lat: 53.29},
|
||||
{Long: 8.30, Lat: 53.30},
|
||||
}, 5, false},
|
||||
|
||||
// L-shape
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.30},
|
||||
{Long: 8.35, Lat: 53.30},
|
||||
}, 8, false},
|
||||
|
||||
// closed line (triangle)
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.30},
|
||||
{Long: 8.35, Lat: 53.30},
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
}, 11, false},
|
||||
// same closed line but polygon (triangle), whole bbox (4x5 tiles) is expired
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.30},
|
||||
{Long: 8.35, Lat: 53.30},
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
}, 20, true},
|
||||
|
||||
// large triangle, only outline expired for polygons and lines
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.90},
|
||||
{Long: 8.85, Lat: 53.90},
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
}, 124, true},
|
||||
{[]element.Node{
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
{Long: 8.30, Lat: 53.90},
|
||||
{Long: 8.85, Lat: 53.90},
|
||||
{Long: 8.30, Lat: 53.25},
|
||||
}, 124, false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
actual := TileCoords(test.long, test.lat, test.zoom)
|
||||
if len(actual) != len(test.tiles) {
|
||||
t.Errorf("%v != %v", actual, test.tiles)
|
||||
continue
|
||||
}
|
||||
for i := range actual {
|
||||
if actual[i] != test.tiles[i] {
|
||||
t.Errorf("%v != %v", actual, test.tiles)
|
||||
tl := NewTileList(14, "")
|
||||
tl.ExpireNodes(test.nodes, test.polygon)
|
||||
if len(tl.tiles) != test.expected {
|
||||
t.Errorf("expected %d tiles, got %d", test.expected, len(tl.tiles))
|
||||
for tk, _ := range tl.tiles {
|
||||
t.Errorf("\t%v", tk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,36 @@
|
|||
<node id="30404" version="1" timestamp="2011-11-11T00:11:11Z" lon="4.0001" lat="-3.0001" />
|
||||
</modify>
|
||||
|
||||
<!-- create polygon (box) -->
|
||||
<create>
|
||||
<node id="40101" version="1" timestamp="2011-11-11T00:11:11Z" lon="1.00" lat="4.00" />
|
||||
<node id="40102" version="1" timestamp="2011-11-11T00:11:11Z" lon="1.05" lat="4.00" />
|
||||
<node id="40103" version="1" timestamp="2011-11-11T00:11:11Z" lon="1.05" lat="4.05" />
|
||||
<node id="40104" version="1" timestamp="2011-11-11T00:11:11Z" lon="1.00" lat="4.05" />
|
||||
<way id="40151" version="1" timestamp="2011-11-11T00:11:11Z">
|
||||
<nd ref="40101"/>
|
||||
<nd ref="40102"/>
|
||||
<nd ref="40103"/>
|
||||
<nd ref="40104"/>
|
||||
<nd ref="40101"/>
|
||||
<tag k="building" v="yes"/>
|
||||
</way>
|
||||
</create>
|
||||
|
||||
|
||||
<!-- create large polygon (outline) -->
|
||||
<create>
|
||||
<node id="40201" version="1" timestamp="2011-11-11T00:11:11Z" lon="2.0" lat="4.00" />
|
||||
<node id="40202" version="1" timestamp="2011-11-11T00:11:11Z" lon="2.6" lat="4.00" />
|
||||
<node id="40203" version="1" timestamp="2011-11-11T00:11:11Z" lon="2.6" lat="4.4" />
|
||||
<node id="40204" version="1" timestamp="2011-11-11T00:11:11Z" lon="2.00" lat="4.4" />
|
||||
<way id="40251" version="1" timestamp="2011-11-11T00:11:11Z">
|
||||
<nd ref="40201"/>
|
||||
<nd ref="40202"/>
|
||||
<nd ref="40203"/>
|
||||
<nd ref="40204"/>
|
||||
<nd ref="40201"/>
|
||||
<tag k="building" v="yes"/>
|
||||
</way>
|
||||
</create>
|
||||
|
||||
</osmChange>
|
||||
|
|
|
@ -124,4 +124,12 @@
|
|||
<tag k="building" v="yes"/>
|
||||
<tag k="type" v="multipolygon"/>
|
||||
</relation>
|
||||
|
||||
|
||||
<!-- create polygon (box) -->
|
||||
<!-- <node id="40101" ...-->
|
||||
|
||||
<!-- create large polygon (outline) -->
|
||||
<!-- <node id="40201" ...-->
|
||||
|
||||
</osm>
|
||||
|
|
|
@ -3,7 +3,6 @@ package test
|
|||
import (
|
||||
"bufio"
|
||||
"database/sql"
|
||||
"github.com/omniscale/imposm3/expire"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -81,49 +80,92 @@ func TestExpireTiles_CheckExpireFile(t *testing.T) {
|
|||
|
||||
for _, test := range []struct {
|
||||
reason string
|
||||
long float64
|
||||
lat float64
|
||||
tiles []tile
|
||||
expire bool
|
||||
}{
|
||||
{"create node", 3, 1, true},
|
||||
{"modify node (old)", 1, 1, true},
|
||||
{"modify node (new)", 1, -1, true},
|
||||
{"modify node to unmapped (old)", 4, 1, true},
|
||||
{"modify node to unmapped (new)", 4, -1, false},
|
||||
{"delete node", 2, 1, true},
|
||||
{"create node", []tile{{8328, 8146, 14}}, true},
|
||||
{"modify node (old)", []tile{{8237, 8146, 14}}, true},
|
||||
{"modify node (new)", []tile{{8237, 8237, 14}}, true},
|
||||
{"modify node to unmapped (old)", []tile{{8373, 8146, 14}, {8374, 8146, 14}}, true},
|
||||
{"modify node to unmapped (new)", []tile{{8373, 8146, 14}, {8374, 8146, 14}}, false},
|
||||
{"delete node", []tile{{8282, 8146, 14}, {8283, 8146, 14}}, true},
|
||||
|
||||
{"delete way", 2.0001, 2, true},
|
||||
{"modify way", 1.0001, 2, true},
|
||||
{"modify way from node (old)", 3.0001, 2, true},
|
||||
{"modify way from node (new)", 3.0001, -2, true},
|
||||
{"create way", 4.0001, 2, true},
|
||||
{"delete way", []tile{{8283, 8100, 14}}, true},
|
||||
{"modify way", []tile{{8237, 8100, 14}}, true},
|
||||
{"modify way from node (old)", []tile{{8328, 8100, 14}}, true},
|
||||
{"modify way from node (new)", []tile{{8328, 8283, 14}}, true},
|
||||
{"create way", []tile{{8374, 8100, 14}}, true},
|
||||
{"create long way", []tile{{8419, 8100, 14}, {8420, 8100, 14}, {8421, 8100, 14}}, true},
|
||||
|
||||
{"create long way (start)", 5.00, 2, true},
|
||||
{"create long way (mid)", 5.025, 2, false}, // TODO not implemented
|
||||
{"create long way (end)", 5.05, 2, true},
|
||||
{"modify relation", []tile{{8237, 8055, 14}}, true},
|
||||
{"delete relation", []tile{{8283, 8055, 14}}, true},
|
||||
{"modify relation from way", []tile{{8328, 8055, 14}}, true},
|
||||
{"modify relation from nodes (old)", []tile{{8374, 8055, 14}}, true},
|
||||
{"modify relation from nodes (new)", []tile{{8374, 8328, 14}}, true},
|
||||
{"create polygon (box)", []tile{
|
||||
{8237, 8007, 14},
|
||||
{8237, 8008, 14},
|
||||
{8237, 8009, 14},
|
||||
{8238, 8007, 14},
|
||||
{8238, 8008, 14},
|
||||
{8238, 8009, 14},
|
||||
{8239, 8007, 14},
|
||||
{8239, 8008, 14},
|
||||
{8239, 8009, 14},
|
||||
}, true},
|
||||
|
||||
{"modify relation", 1.0001, 3, true},
|
||||
{"delete relation", 2.0001, 3, true},
|
||||
{"modify relation from way", 3.0001, 3, true},
|
||||
{"modify relation from nodes (old)", 4.0001, 3, true},
|
||||
{"modify relation from nodes (new)", 4.0001, -3, true},
|
||||
{"create polygon (outline)", []tile{
|
||||
{8310, 8005, 14}, {8302, 7991, 14}, {8283, 7993, 14},
|
||||
{8300, 8009, 14}, {8283, 8003, 14}, {8308, 8009, 14},
|
||||
{8310, 7995, 14}, {8285, 8009, 14}, {8288, 8009, 14},
|
||||
{8301, 8009, 14}, {8310, 8002, 14}, {8302, 8009, 14},
|
||||
{8310, 8003, 14}, {8286, 8009, 14}, {8300, 7991, 14},
|
||||
{8283, 7994, 14}, {8296, 8009, 14}, {8298, 8009, 14},
|
||||
{8310, 8009, 14}, {8283, 7999, 14}, {8283, 7992, 14},
|
||||
{8290, 7991, 14}, {8305, 8009, 14}, {8309, 7991, 14},
|
||||
{8306, 7991, 14}, {8291, 7991, 14}, {8283, 7996, 14},
|
||||
{8310, 7996, 14}, {8293, 7991, 14}, {8310, 8007, 14},
|
||||
{8310, 8001, 14}, {8307, 8009, 14}, {8299, 8009, 14},
|
||||
{8310, 7998, 14}, {8310, 7999, 14}, {8301, 7991, 14},
|
||||
{8283, 7998, 14}, {8283, 8006, 14}, {8289, 8009, 14},
|
||||
{8310, 8008, 14}, {8285, 7991, 14}, {8283, 8002, 14},
|
||||
{8289, 7991, 14}, {8286, 7991, 14}, {8288, 7991, 14},
|
||||
{8283, 8008, 14}, {8283, 8005, 14}, {8310, 7992, 14},
|
||||
{8310, 8004, 14}, {8310, 7991, 14}, {8296, 7991, 14},
|
||||
{8292, 7991, 14}, {8283, 8009, 14}, {8291, 8009, 14},
|
||||
{8293, 8009, 14}, {8284, 8009, 14}, {8287, 7991, 14},
|
||||
{8297, 8009, 14}, {8283, 8007, 14}, {8299, 7991, 14},
|
||||
{8310, 7997, 14}, {8303, 8009, 14}, {8290, 8009, 14},
|
||||
{8306, 8009, 14}, {8283, 7995, 14}, {8283, 8000, 14},
|
||||
{8295, 8009, 14}, {8310, 8006, 14}, {8304, 8009, 14},
|
||||
{8295, 7991, 14}, {8292, 8009, 14}, {8309, 8009, 14},
|
||||
{8283, 8004, 14}, {8307, 7991, 14}, {8305, 7991, 14},
|
||||
{8283, 8001, 14}, {8284, 7991, 14}, {8297, 7991, 14},
|
||||
{8310, 7993, 14}, {8303, 7991, 14}, {8294, 8009, 14},
|
||||
{8287, 8009, 14}, {8283, 7991, 14}, {8283, 7997, 14},
|
||||
{8308, 7991, 14}, {8304, 7991, 14}, {8298, 7991, 14},
|
||||
{8310, 8000, 14}, {8310, 7994, 14}, {8294, 7991, 14},
|
||||
}, true},
|
||||
} {
|
||||
for _, coord := range expire.TileCoords(test.long, test.lat, 14) {
|
||||
x, y := coord.X, coord.Y
|
||||
|
||||
for _, coord := range test.tiles {
|
||||
if test.expire {
|
||||
if _, ok := tiles[tile{x: int(x), y: int(y), z: 14}]; !ok {
|
||||
t.Errorf("missing expire tile for %s 14/%d/%d for %f %f", test.reason, x, y, test.long, test.lat)
|
||||
if _, ok := tiles[coord]; !ok {
|
||||
t.Errorf("missing expire tile for %s %v", test.reason, coord)
|
||||
} else {
|
||||
delete(tiles, tile{x: int(x), y: int(y), z: 14})
|
||||
delete(tiles, coord)
|
||||
}
|
||||
} else {
|
||||
if _, ok := tiles[tile{x: int(x), y: int(y), z: 14}]; ok {
|
||||
t.Errorf("found expire tile for %s 14/%d/%d for %f %f", test.reason, x, y, test.long, test.lat)
|
||||
if _, ok := tiles[coord]; ok {
|
||||
t.Errorf("found expire tile for %s %v", test.reason, coord)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(tiles) > 0 {
|
||||
t.Errorf("found %d unexpected tiles", len(tiles))
|
||||
}
|
||||
for tile, _ := range tiles {
|
||||
t.Errorf("unexpected tile expired: %v", tile)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue