Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 94 additions & 72 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,112 @@ import (
"image"
"image/color"
"image/draw"
"math"
"time"

"code.google.com/p/draw2d/draw2d"
"code.google.com/p/freetype-go/freetype"
"code.google.com/p/freetype-go/freetype/truetype"
"github.com/golang/geo/s2"
"github.com/llgcode/draw2d/draw2dimg"
)

const tileSize = 256

func DrawTile(nwPt, sePt Pointer, zoom int64, font *truetype.Font, data *OsmData, debug bool) (image.Image, error) {
func DrawTile(nwPt, sePt Pointer, zoom int64, data *OsmData, debug bool) (image.Image, error) {
t := time.Now()

// s2.Points of tile 4 vertex
p1 := s2.PointFromLatLng(s2.LatLngFromDegrees(nwPt.Lat(), nwPt.Lon()))
p2 := s2.PointFromLatLng(s2.LatLngFromDegrees(sePt.Lat(), nwPt.Lon()))
p3 := s2.PointFromLatLng(s2.LatLngFromDegrees(nwPt.Lat(), sePt.Lon()))
p4 := s2.PointFromLatLng(s2.LatLngFromDegrees(sePt.Lat(), sePt.Lon()))
// Create white image
img := image.NewRGBA(image.Rect(0, 0, tileSize, tileSize))
draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Src)

// Plot some features
for fName, features := range data.Features {
if mapFeatures[fName].MinZoom > zoom {
for _, feature := range data.Findex.GetFeatures(nwPt, sePt, zoom) {
if mz, ok := mapFeatures[feature.FName]; ok {
if mz.MinZoom > zoom {
continue
}
} else {
continue
}

for _, feature := range features {
switch feature.Type {
case ItemTypeNode:
//TODO
case ItemTypeWay:
way := data.Ways[feature.Id]
//TODO: this ignores ways crossing over the tile
withinBounds := false
for _, node := range way.GetNodes(data.Nodes) {
switch feature.Type {
case ItemTypeNode:
//TODO
case ItemTypeWay:
way := data.Ways[feature.Id]
var (
// previous plotting point, nil if already added to coords or first time
prevCoords []float64
// previous s2 Point. Always present, except first time
prevS2Point s2.Point
)
coords := [][]float64{}
prevWithinBounds := false
first := true
for _, node := range way.GetNodes(data.Nodes) {
if first {
first = false
x, y := getRelativeXY(nwPt, node, float64(zoom))
if node.Lon() > nwPt.Lon() && node.Lon() < sePt.Lon() &&
node.Lat() < nwPt.Lat() && node.Lat() > sePt.Lat() {
withinBounds = true
coords = append(coords, []float64{x, y})
prevWithinBounds = true
} else {
//x, y := getRelativeXY(nwPt, node, float64(zoom))
//a1 := s2.PointFromLatLng(s2.LatLngFromDegrees(node.Lat(), node.Lon()))
prevS2Point = s2.PointFromLatLng(s2.LatLngFromDegrees(node.Lat(), node.Lon()))
prevCoords = []float64{x, y}
prevWithinBounds = false
}
}

if !withinBounds {
continue
}

coords := [][]float64{}
for _, node := range way.GetNodes(data.Nodes) {
if node.Lon() > nwPt.Lon() && node.Lon() < sePt.Lon() &&
node.Lat() < nwPt.Lat() && node.Lat() > sePt.Lat() {
if prevWithinBounds == false {
if len(prevCoords) > 0 {
coords = append(coords, prevCoords)
}
}
x, y := getRelativeXY(nwPt, node, float64(zoom))
coords = append(coords, []float64{x, y})
prevWithinBounds = true
prevCoords = nil
} else {
x, y := getRelativeXY(nwPt, node, float64(zoom))
a1 := s2.PointFromLatLng(s2.LatLngFromDegrees(node.Lat(), node.Lon()))
if prevWithinBounds == true {
coords = append(coords, []float64{x, y})
prevCoords = nil //already added to coords
} else {

if s2.SimpleCrossing(p1, p2, a1, prevS2Point) || s2.SimpleCrossing(p1, p3, a1, prevS2Point) || s2.SimpleCrossing(p2, p4, a1, prevS2Point) {
if len(prevCoords) > 0 {
coords = append(coords, prevCoords)
}
coords = append(coords, []float64{x, y})
prevCoords = nil
} else {
if len(coords) > 0 {
drawPolyLine(img, color.Black, coords)
coords = coords[:0]
}
prevCoords = []float64{x, y}
}
}
prevS2Point = a1
prevWithinBounds = false
}
}

if len(coords) > 0 {
drawPolyLine(img, color.Black, coords)

case ItemTypeRelation:
//TODO
}

case ItemTypeRelation:
//TODO
}
}

Expand All @@ -72,13 +125,13 @@ func DrawTile(nwPt, sePt Pointer, zoom int64, font *truetype.Font, data *OsmData
}

// Tile location
err := drawText(img, font, red, tileSize/2, 20, fmt.Sprintf("%f, %f", nwPt.Lon(), nwPt.Lat()))
err := drawText(img, red, tileSize/2, 20, fmt.Sprintf("%f, %f", nwPt.Lon(), nwPt.Lat()))
if err != nil {
return nil, err
}

// Tile location
err = drawText(img, font, red, tileSize/2, tileSize-20, time.Since(t).String())
err = drawText(img, red, tileSize/2, tileSize-20, time.Since(t).String())
if err != nil {
return nil, err
}
Expand All @@ -87,54 +140,23 @@ func DrawTile(nwPt, sePt Pointer, zoom int64, font *truetype.Font, data *OsmData
return img, nil
}

func drawText(img *image.RGBA, font *truetype.Font, color color.Color, x, y int, s string) error {
var ptSize float64 = 12

ctx := freetype.NewContext()
ctx.SetDPI(72)
ctx.SetFont(font)
ctx.SetFontSize(ptSize)
ctx.SetClip(img.Bounds())
ctx.SetDst(img)
ctx.SetSrc(image.NewUniform(color))
ctx.SetHinting(freetype.FullHinting)

width := int(widthOfString(font, ptSize, s))
pt := freetype.Pt(x-width/2, y+int(ctx.PointToFix32(ptSize)>>8)/2)
_, err := ctx.DrawString(s, pt)
if err != nil {
return err
}
func drawText(img *image.RGBA, cc color.Color, x, y int, s string) error {

path := draw2dimg.NewGraphicContext(img)
path.SetStrokeColor(cc)
path.SetFillColor(cc)
path.SetDPI(72)
path.StrokeStringAt(s, float64(x), float64(y))

return nil
}

// https://code.google.com/p/plotinum/source/browse/vg/font.go#160
func widthOfString(font *truetype.Font, size float64, s string) float64 {
// scale converts truetype.FUnit to float64
scale := size / float64(font.FUnitsPerEm())

width := 0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := font.Index(rune)
if hasPrev {
width += int(font.Kerning(font.FUnitsPerEm(), prev, index))
}
width += int(font.HMetric(font.FUnitsPerEm(), index).AdvanceWidth)
prev, hasPrev = index, true
}

return float64(width) * scale
}
func drawPolyLine(img *image.RGBA, cc color.Color, coords [][]float64) {
path := draw2dimg.NewGraphicContext(img)

func round(n float64) int {
//TODO: this is incorrect
return int(math.Floor(n))
}
path.SetStrokeColor(cc)
path.SetLineWidth(1)

func drawPolyLine(img *image.RGBA, color color.Color, coords [][]float64) {
path := draw2d.NewPathStorage()
for i, coord := range coords {
if i == 0 {
path.MoveTo(coord[0], coord[1])
Expand All @@ -143,7 +165,7 @@ func drawPolyLine(img *image.RGBA, color color.Color, coords [][]float64) {
}
}

gc := draw2d.NewGraphicContext(img)
gc.SetStrokeColor(color)
gc.Stroke(path)
// TODO check area tag ?
// path.Close()
path.Stroke()
}
16 changes: 1 addition & 15 deletions draw_test.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
package tiles

import (
"io/ioutil"
"testing"

"code.google.com/p/freetype-go/freetype"
)

func BenchmarkDrawTile(b *testing.B) {
nwPt := Point{-4.482421875, 54.162433968067795}
sePt := Point{-4.471435546875, 54.156001090284924}
scale := int64(15)

// Read font
font_, err := ioutil.ReadFile("example/FiraSans-Regular.ttf")
if err != nil {
b.Fatalf("Benchmark setup failed: %#v\n", err)
}

font, err := freetype.ParseFont(font_)
if err != nil {
b.Fatalf("Benchmark setup failed: %#v\n", err)
}

// Read PBF file
data, err := ParsePbf("example/isle-of-man-latest.osm.pbf")
if err != nil {
Expand All @@ -32,7 +18,7 @@ func BenchmarkDrawTile(b *testing.B) {
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := DrawTile(nwPt, sePt, scale, font, data, false)
_, err := DrawTile(nwPt, sePt, scale, data, false)
if err != nil {
b.Fatalf("Received error: %#v\n", err)
}
Expand Down
Binary file removed example/FiraSans-Regular.ttf
Binary file not shown.
Binary file added example/luxisr.ttf
Binary file not shown.
4 changes: 3 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"

"github.com/holizz/go-tile-server"
"github.com/llgcode/draw2d"
)

func main() {
Expand All @@ -16,7 +17,8 @@ func main() {
port = 3000
}

tileHandler := tiles.NewTileHandler("/tiles", "isle-of-man-latest.osm.pbf", "FiraSans-Regular.ttf")
draw2d.SetFontFolder(".")
tileHandler := tiles.NewTileHandler("/tiles", "isle-of-man-latest.osm.pbf")

http.Handle("/tiles/", tileHandler)
http.Handle("/", http.FileServer(http.Dir("public")))
Expand Down
54 changes: 51 additions & 3 deletions features.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,56 @@ var mapFeatures = map[string]Feature{
{"natural", "coastline"},
},
},
"all-other-roads": {
"borders2": {
MinZoom: 0,
Tags: []Tag{
{"admin_level", "2"},
},
},
"borders3": {
MinZoom: 6,
Tags: []Tag{
{"admin_level", "3"},
},
},
"borders4": {
MinZoom: 8,
Tags: []Tag{
{"admin_level", "4"},
},
},
"borders5": {
MinZoom: 10,
Tags: []Tag{
{"admin_level", "5"},
},
},
"borders6": {
MinZoom: 12,
Tags: []Tag{
{"admin_level", "6"},
},
},
"borders7": {
MinZoom: 12,
Tags: []Tag{
{"admin_level", "7"},
},
},
"borders8": {
MinZoom: 13,
Tags: []Tag{
{"admin_level", "8"},
},
},
"borders9": {
MinZoom: 14,
Tags: []Tag{
{"admin_level", "9"},
},
},
"all-other-roads": {
MinZoom: 12,
Tags: []Tag{
{"highway", "unclassified"},
{"highway", "residential"},
Expand All @@ -35,15 +83,15 @@ var mapFeatures = map[string]Feature{
},
},
"major-ish-roads": {
MinZoom: 12,
MinZoom: 9,
Tags: []Tag{
{"highway", "primary"},
{"highway", "secondary"},
{"highway", "tertiary"},
},
},
"major-major-roads": {
MinZoom: 10,
MinZoom: 6,
Tags: []Tag{
{"highway", "motorway"},
{"highway", "trunk"},
Expand Down
Loading